Tuesday, February 4, 2014

Allview Speed City/Onda 701 touch screen replacement

After about a year of use (and some hard drops) the touchscreen in my Allview Speed City tablet (Onda 701 clone) stopped working in a 1/3 region of the screen. I could still use the tablet by rotating it when I needed to tap in the broken area, but I needed to replace the touchscreen.

I got a replacement touchscreen from here: http://www.gsmnet.ro/touchscreen-uri-telefoane/touchscreen-allview-alldro-speed-city-original--81391 for about $30.

I followed the replacement guide in this video: http://www.dailymotion.com/video/x17mxag_touch-screen-replacement-onda-v701-v702-v703-v701s-tablet-disassembly_lifestyle

The disassembly (and reassembly process) took me about 2 hours, but was not very difficult. The first thing to do is to remove the rubber caps on top of the screws in the back of the tablet (use a needle/pincer). You can unscrew the screws (small phillips head).

The tablets back cover is glued on (the screws don't hold it actually). You will need something like a credit card to separate the back cover from the tablet chassis.



Next, you can separate the screen unit from the chassis by inserting the credit card between them (start with a side). The screen detaches quickly once you manage to separate one clip.


At this point I still had the tablet turned on and I could test the connectors (to see if the touchscreen connector was loose). I took out the connector and replaced it with the new touch screen and tried out the new touch screen before removing the old one.

Here are some more images of the tablet's internals:
Mainboard overview

Processor and RAM
Internal storage FLASH chip and screen (left) and touchscreen (right) connectors. In the bottom left you can see some solder points for something that looks like a serial port (haven't tested it).
Top part - camera connection and radios (you can see a Realtek chip)

When you are ready, detach the touchscreen + screen from the mainboard (2 connectors). The touchscreen is glued onto the screen and it will take a while to detach. Use a knife and start at a corner. As you can see, the touchscreen has 2 layers - a transparent plastic on top and an adhesive plastic between the top and the screen. You will need to detach the adhesive plastic completely! It may look like glass, but it's not. You should not be able to cut yourself in it, but take care because it breaks into small shards.

Touchscreen layers. You can see some of the circuitry for the touch sensors
Old touchscreen detached (and broken in the process). Some debris are left on the screen mount.


Once the top comes off you can add the new touchscreen (after you've cleared all the residues from the panel. Before gluing the top on, make some alignment tests - you want the touchscreen to be perfectly aligned on top of the screen. Prior to the attachment make sure to clean both the screen and the back side of the  touchscreen with a cloth to remove any finger marks or debris.


Try out to position and align the touchscreen correctly before removing the white bands

Remove the paper protections on the new touchscreen and glue it on the screen mount. Once it is glued on, connect the ribbon cables back to the motherboard and try it out. If everything is ok, reassemble the tablet (you may need to use some glue to hold the back case in place).

Overall the operation is not difficult to perform. The end result was satisfying, but the replacement touchscreen has some "blind spots" - regions where it doesn't easily register touch events. I could see these spots before when testing the new touchscreen. In terms of alignment I was about 1.5mm off (the touchscreen is about 1.5mm too high), and I have a small open region in the bottom of the tablet. It doesn't impact functionality in any way.

Cosmetic alignment fault (1.5mm off).


Good luck with your replacement!

Monday, April 8, 2013

Rooting Allview Speed City

I was looking for a budget tablet with decent hardware (except for the screen which is crappy) so I settled on the Allview Speed City. This tablet is similar to the Chinese Onda 701 with the small difference that the Speed City does not have Volume Up/Down hardware buttons. The same hardware may be marketed and sold under other names in various countries, so check the specs before attempting to root it.

The tablet is running a custom ROM based on Android 4.1.1 that includes some bloatware (like an antivirus) next to the standard apps. Fortunately, it has a recovery ROM that lets you install zip files, so, it doesn't appear to be fully locked.

So, as any power user, I wanted to root the tablet and be the master of the software installed. Problem is - there was no known root available when I bought it.

Note: before proceeding make sure you understand the risks of rooting, and also be advised that you may lose your warranty.

Rooting instructions for the impacient

So without much ado, to root the tablet, you will need to follow these steps:
  1. Download the root + google apps package: http://www.mediafire.com/download.php?bbuqt6rwxou7cw2 (if the link is no longer valid drop a comment and I will re-upload it)
  2. Unzip the archive downloaded above (Root_Gapps_complete.zip) to an external SD card in the root of that card. You will get two files:  factory_update_param.aml and root.zip.
  3. Plug in the SD card into the tablet (the procedure requires an external SD card; internal storage doesn't work).
  4. Turn off the tablet
  5. Turn on the tablet in recovery mode. You do this by holding the HOME button and the POWER button pressed until you will see a big green android on the screen. You can release the buttons now. (the screen looks like the following image - sorry for the quality)
  6. Once the update is complete, the tablet will reboot automatically and you will get a message "Updating system apps". Once it's finished, you are rooted (if Superuser is not installed, you can install it from the market).
  7. Profit! :)

 The emergency ROM

All Android devices have a recovery ROM that allows you to unbrick your device if tragedy strikes. For the Allview Speed City you can enter in the Recovery ROM by holding the HOME and POWER buttons pressed at startup. You will be presented with something like this:



Since unlike the Onda 701 you do not have hardware keys for Volume Up/Down, you'll need to use the HOME button as DOWN ARROW and the POWER button as ENTER.

Other than that, the Recovery ROM is pretty functional.

Extra resources: Visit this link to learn more about rooting this tablet and alternate firmwares for it: http://forum.xda-developers.com/showthread.php?t=2119354
Also, special thanks to the members of the XDA community who made this possible.

Monday, January 14, 2013

Android: Disabling Battery full alert

I keep hearing in my dreams a sort of "ding" sound made by my phone (running Android 4.1). It's trying to tell me to disconnect the charger from the wall socket (to be greener), but unfortunately it's doing this in the middle of the night, and it's annoying. There is no risk to damage your phone if you keep it plugged in beyond this point anyway.

Well, there seem to be apps that let you manage the notification sound (like Battery full notification), but they seem overkill for what I want.

Luckly, I found this thread in my searches: http://androidforums.com/samsung-galaxy-s2-international/331058-stopping-fully-charged-sound.html#post2819281

If you have a rooted phone, you can follow the instructions in the thread, or follow these steps in a terminal emulator on your phone:

su
cd /system/media/audio/ui
mount -o remount,rw /system
mv TW_Battery_caution.ogg TW_Battery_caution.ogg.bak
mount -o remount,ro /system
exit
exit
 It renames the notification sound, so that next time your phone wants to wake you up to unplug the charger, it will have no voice :P

Wednesday, January 9, 2013

Android: Adding scp/sftp support to dropbear and mounting with sshfs

I have recently received an android smartphone, and one of the first things I did with it was to root it :). This allows power-usres to get the most out of their hardware.

The next thing on my list was to set up a SSH server and to be able to transfer files between my Linux system and my phone (by the way, I'm running Android 4.1 and it seems USB mass storage support has been removed. MTP/PTP modes have either horrible transfer speed or are poorly supported in Ubuntu).

With the above in mind, the plan was to:
  • Enable tethering on the phone
  • Run a SSH server to support issuing remote commands and file transfer (FTP might have been an alternative, but I'm a SSH adept).
Browsing the market I found SSHDroid which does all that it advertised. Problem is - the free version conflicts with my add-blocking apps and requests that they are disabled to run.

For me, this is a big nuisance, so I kept looking. I found Dropbear SSH Server 2, which is completely free, but doesn't support scp/sftp.

So, I wanted scp/sftp support, so I started to work on a solution.

If, after starting Dropbear server you try to transfer a file via scp you get this error (pris is the name of my phone and is mapped to an IP address via the /etc/hosts file):
adrianp@frost:~$ scp test.log root@pris:/storage/sdcard0/
Welcome to DropBear SSH Server II!
root@pris's password:
sh: scp: not found
lost connection
adrianp@frost:~$
then, you are in the same situation I was...

For scp/sftp to work, the process needs to have access to the scp/sftp-server binaries on your android system. But it seems dropbear doesn't come with those binaries. But searching around the system, the binaries are available in the SSHDroid package.

So, I was doing the following steps to make those binaries available to the whole system (needs a rooted system with busybox installed):

root@android:/data/local # scp
sh: scp: not found
127|root@android:/data/local # find / -name scp 2>/dev/null

/data/data/berserker.android.apps.sshdroid/dropbear/scp
1|root@android:/data/local # /data/data/berserker.android.apps.sshdroid/dropbear/scp
usage: scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]
           [-l limit] [-o ssh_option] [-P port] [-S program]
           [[user@]host1:]file1 [...] [[user@]host2:]file2

1|root@android:/data/local # ls -l /system/xbin/                              
-rwxr-xr-x root     shell       59760 2012-09-28 12:15 dexdump
-rwsr-sr-x root     root        91992 2012-12-21 10:02 su
root@android:/data/local # echo $PATH                                         
/sbin:/system/sbin:/system/bin:/system/xbin
root@android:/data/local # mount -o remount,rw /system
root@android:/data/local # ln -s /data/data/berserker.android.apps.sshdroid/dropbear/scp /system/xbin/scp

root@android:/data/local # ln -s /data/data/berserker.android.apps.sshdroid/dropbear/ssh /system/xbin/ssh
root@android:/data/local # ln -s /data/data/berserker.android.apps.sshdroid/dropbear/sftp-server /system/xbin/sftp-server
root@android:/data/local # mount -o remount,ro /system
At this point, you can use scp to transfer files from your computer to your Android device. However, you can't mount it with sshfs yet.

The problem when mounting it is that sshfs tries to use the sftp server, and by default it tries to call it from /usr/libexec/sftp-server. This path does not exist on your android device, and you will need to instruct sftp to use /system/xbin/sftp-server instead. You can do this with the following command:

sudo sshfs -o sftp_server=/system/xbin/sftp-server  root@pris:/storage /media/pris
Of course, in order to mount the device as a regular user and to be able to transfer files, you will need to prepare your mount point and your fstab entry:

adrianp@frost:~/temp$ sudo mkdir -p /media/pris
adrianp@frost:~/temp$ sudo chown root:fuse /media/pris
adrianp@frost:~/temp$ sudo chmod g+w /media/pris
adrianp@frost:~/temp$ cat /etc/fstab | grep pris
sshfs#root@pris:/storage /media/pris fuse user,fsname=sshfs#root@pris:/storage,noauto,sftp_server=/system/xbin/sftp-server 0 0
You will now be able to mount the device via the command line (or in Nautilus). It will ask for your ssh password, and then it will display the files as if they were local. In terms of performance, I get about 4.6MB/s reads and 4.1MB/s write speed (over USB).  Compared to ~240kB/s read/write in MTP mode, I would say I get quite a performance boost!

Remember, in order to follow the steps above you will need:
  • a rooted android device
  • busybox installed
  • SSHDroid installed (it will remain installed even if you don't start the ssh service. You can possibly uninstall it if you replace the "ln -s" commands with "cp" instead)
  • Dropbear II (and started)
  • either a wifi connection between your PC and android device, or USB tethering (it's what I'm using)
Enjoy!

Friday, November 16, 2012

Extracting the firmware for Edimax IC-7110W IP Camera

I got an Edimax IC-7110W IP camera and I liked their firmware, but I was curious what was happening behind the curtains, so I decided to take a look.

Step 1: Get the firmware - I got the firmware update binary package from their site for version 1.7 - other versions are probably similar: http://www.edimax.com/en/support_detail.php?pd_id=415&pl1_id=8

Step 2: Prepare your environment - I am using Ubuntu Linux 12.04. You will need to download and install the following software:
Step 3: Install firmware-mod-kit and compile binwalk and unsquashfs:

adrianp@frost:~/temp$ mkdir -p edimax/1.7 edimax/fmk
adrianp@frost:~/temp$ cd edimax
adrianp@frost:~/temp/edimax$ svn checkout http://firmware-mod-kit.googlecode.com/svn/trunk/ fmk

... output omitted ...

adrianp@frost:~/temp/edimax$ cd fmk/src/binwalk-0.4.1/src
adrianp@frost:~/temp/edimax/fmk/src/binwalk-0.4.1/src$ ./configure

... output omitted ...

adrianp@frost:~/temp/edimax/fmk/src/binwalk-0.4.1/src$ make

... output omitted ...

adrianp@frost:~/temp/edimax/fmk/src/binwalk-0.4.1/src$ ls -l binwalk
-rwxrwxr-x 1 adrianp adrianp 358991 Nov 16 16:15 binwalk
 
adrianp@frost:~/temp/edimax/fmk/src/binwalk-0.4.1/src$ cd ../../squashfs-3.0/
adrianp@frost:~/temp/edimax/fmk/src/squashfs-3.0$ make

... output omitted ...adrianp@frost:~/temp/edimax/fmk/src/squashfs-3.0$ ls -l unsquashfs*
-rwxrwxr-x 1 adrianp adrianp  34292 Nov 16 16:18 unsquashfs
-rwxrwxr-x 1 adrianp adrianp 227552 Nov 16 16:18 unsquashfs-lzma

adrianp@frost:~/temp/edimax/fmk/src/squashfs-3.0$ cd ../../../  
adrianp@frost:~/temp/edimax$

Step 4: Unzip the firmware and extract the bin file:

adrianp@frost:~/temp/edimax$ cd 1.7
adrianp@frost:~/temp/edimax/1.7$ lsIC-7110_EDIMAX_CLOUD_v1.7_upg.zip
adrianp@frost:~/temp/edimax/1.7$ unzip IC-7110_EDIMAX_CLOUD_v1.7_upg.zip
Archive:  IC-7110_EDIMAX_CLOUD_v1.7_upg.zip
  inflating: IC-7110_EDIMAX_CLOUD_v1.7_upg.bin 
adrianp@frost:~/temp/edimax/1.7$ ls -l
total 7296
-rw-rw-r-- 1 adrianp adrianp 3751945 Apr 18  2012 IC-7110_EDIMAX_CLOUD_v1.7_upg.bin
-r-------- 1 adrianp adrianp 3709299 Nov 16 15:47 IC-7110_EDIMAX_CLOUD_v1.7_upg.zip

Step 5: Use binwalk to extract the root filesystem from the firmware (change the relative path to binwalk if needed) (note - analysing the firmware might take up to 5-10 minutes):

adrianp@frost:~/temp/edimax/1.7$ ../fmk/src/binwalk-0.4.1/src/binwalk -m ../fmk/src/binwalk-0.4.1/src/magic.binwalk  IC-7110_EDIMAX_CLOUD_v1.7_upg.bin

DECIMAL       HEX           DESCRIPTION
-------------------------------------------------------------------------------------------------------
11388         0x2C7C        gzip compressed data, from Unix, last modified: Wed Apr 18 05:12:23 2012, max compression
786440        0xC0008       Squashfs filesystem, little endian, version 3.0, size: 2961974 bytes, 221 inodes, blocksize: 65536 bytes, created: Wed Apr 18 05:12:31 2012

I am not sure what the first entry is - could be the kernel, but we are currently interested in the second one - the root file system.

 Step 6: Extract the root file system from the firmware file. Right now the root file system is embedded in the firmware file, starting from offset 0xC0008 (786440 bytes into the file). We need to make it a standalone file. The file size is 2961974 bytes. We will use dd for the job:


adrianp@frost:~/temp/edimax/1.7$ dd if=IC-7110_EDIMAX_CLOUD_v1.7_upg.bin skip=786440 bs=1 count=2961974 of=rootfs.squasfs
2961974+0 records in
2961974+0 records out
2961974 bytes (3.0 MB) copied, 8.89102 s, 333 kB/s
adrianp@frost:~/temp/edimax/1.7$ file rootfs.squasfs
rootfs.squasfs: Squashfs filesystem, little endian, version 3.0, 2961974 bytes, 221 inodes, blocksize: 65536 bytes, created: Wed Apr 18 05:12:31 2012
Step 7: Unsquash the squashfs file. This action decompresses the filesystem and recreates the folder structure it came from. The particular bit is you need to use the same unsquashfs version (3.0) it was created with. One more important detail is that squashfs usually uses gzip as a compressor, but in Edimax's case they used lzma, so you need to use the following command:

adrianp@frost:~/temp/edimax/1.7$ ../fmk/src/squashfs-3.0/unsquashfs-lzma rootfs.squasfs

created 66 files
created 27 directories
created 128 symlinks
created 0 devices
created 0 fifos
Step 8: Profit! Your firmware's root file system is now dumped in the folder squashfs-root:

adrianp@frost:~/temp/edimax/1.7$ cd squashfs-root/
adrianp@frost:~/temp/edimax/1.7/squashfs-root$ ls
bin  dev  etc  lib  linuxrc  mnt  proc  sbin  storage  tmp  usr  var

I will explore some of the hidden features of the firmware in a following blog post.

Thursday, November 15, 2012

RTSP streaming over dual nat with RTSP Interleaved mode

I have an IP Camera that supports RTSP streaming (most IP cameras seem to support it - Axis, Edimax, etc). This allows you to view mpeg4 or h264 live streams either by using a proprietary ActiveX control (via their web interface) or via a RTSP client.

If you are in the same LAN as the camera, you can easily connect to it by using VLC or even ffmpeg (to save or transcode the stream):

vlc rtsp://192.168.1.10/ipcamera_h264.sdp
ffmpeg -i rtsp://192.168.1.10/ipcamera_h264.sdp test.ts
ffmpeg -i rtsp://192.168.1.10/ipcamera_h264.sdp -f mpegts | vlc -



The problem is if you want to access that camera over the Internet. RTSP usually transports the data over UDP and negotiates the UDP ports over a control session on port 554 TCP. This gets difficult if you use NAT at either end or if you have firewalls you need to get through.

Let's consider this typical scenario:



The packets need to go through 2 NATs to travel between the source and destination. To make this happen, you have 3 possible solutions:

1. Using a session management protocol like ICE, but it needs to be supported by the client and server. Cheap IP cameras don't usually support it
2. Using a Layer 3 or Layer 2 VPN between RouterA and RouterB. With a VPN set in place (and with the appropriate firewall permits), the client can connect via RTSP to the server and it would communicate the same way it does in a LAN environment (no more NAT!). However, your routers need to be configured for site-to-site VPN - which might be challenging (especially if you don't have management access on either router)
3. Using RTSP Interleaved mode - a method I will describe next

According to Wikipedia:
Certain firewall designs and other circumstances may force a server to interleave RTSP methods and stream data. This interleaving should generally be avoided unless necessary since it complicates client and server operation and imposes additional overhead. Interleaved binary data SHOULD only be used if RTSP is carried over TCP.

So, instead of using UDP to transfer data, it uses TCP, and furthermore, it piggybacks the video data on top of the control session that is established on port 554. It may be less efficient in payload size and processing power, but it works with NAT and firewalls - which is what we want.

So, for the example above we have the following prerequisites for this to work:
  1.  The ability to port forward on RouterB
  2.  The IP Camera must have a fixed IP address (in order for port forwarding to work). This can be done either by assigning a static IP manually, or through DHCP.
  3.  Client and server must support RTSP Interleaved mode


Port forwarding setup

Depending on your router's firmware this can be done in different ways. I will not show a specific way to do it, just the concept. On RouterB you need to allow incoming TCP packets from source IP 11.11.11.11 (the NATed IP of the client) to go to your IP camera's IP (192.168.1.10) on the RTSP port (554 by default).

Since this is port forwarding you are actually forwarding a TCP port on your router (e.g. 22.22.22.22:1234) to an internal server in your network (192.168.1.10:554), so you will need to decide which external port you will be using (I used 1234 in my example).

If you have multiple IP Cameras behind RouterB, you can add multiple port forwarding rules - like this:

allow TCP from 11.11.11.11 to 192.168.1.10:554 on external port 1234
allow TCP from 11.11.11.11 to 192.168.1.11:554 on external port 5678
The port forwarding rule does not necessarily have to specify the source address - if it is missing it will allow access from any source address - but this is a security risk, so I advise against it.

RTSP Interleaved mode
Right now, you should be able to connect to the camera from the client computer, but if you try to use RTSP, you will notice that the control session is established, but the data never arrives because the NAT and firewall prevent the communication on the negotiated UDP ports.

You must convince the client software to try connecting in interleaved mode (this is why you came here for, right?).

VLC: Well, according to http://www.wowza.com/forums/content.php?64 you need to do the following configuration in VLC:

  1. Open VLC
  2. Select menu item Tools: Preferences
  3. Select the Input & Codecs section
  4. Select the Live555 stream transport option RTP over RTSP (TCP)
  5. Click Save button
You can now connect to your stream using the command:

vlc rtsp://22.22.22.22:1234/ipcamera_h264.sdp
For other cameras, change the destination port (e.g. 1234 -> 5678) in your command

FFMPEG: Their documentation states that you need to use the rtsp_transport flag:

ffmpeg -rtsp_transport tcp -i rtsp://22.22.22.22:1234/ipcamera_h264.sdp test.ts
ffmpeg -rtsp_transport tcp -i rtsp://22.22.22.22:1234/ipcamera_h264.sdp -f mpegts | vlc -
In case of problems, you should analyse a packet capture and see if Interleaved mode is supported by both end systems (it is negotiated in a RTSP OPTIONS request) - capture example below:


Have fun streaming!

Wednesday, November 24, 2010

Converting shapefiles (SHP) to a different projection

I've written a small perl script to handle converting the coordinates in shapefiles to a different coordinate set. You must specify the source EPSG projection and the destination EPSG projection, and the script will walk through the data and create a new shapefile with the coordinates transformed by using the new projection. The new files are saved next to the original files, with the projection number appended to the file names.

Currently the script supports POINT data and LINESTRING data. All other geometries are ignored.

In order to use this script you will need to have the following packages installed (example for ubuntu): shapelib, proj-bin, gdal-bin

Sample usage:
adrianp@frost:~/bin$ ./convertSHPProjection.pl epsg:4326 epsg:31700 ofm_fiber.shp
Going to save data as ofm_fiber_31700.[shp/shx/dbf]
Depending on your data size, this may take a while...
Copying dbf file...
Code:

#!/usr/bin/perl
use strict;
use warnings;

# Author: Adrian Popa
# License: GPLv2/3

# This script reprojects a shape file from a source projection to a destination projection by
# reprojecting each coordinate in the file. The destination file will be named like the source
# file with a suffix containing the projection code.

# This script requires the following binaries to be available (adjust the path to fit your system):
# On ubuntu you can get these binaries by running sudo apt-get install shapelib proj-bin gdal-bin
my $shpcreate = '/usr/bin/shpcreate';
my $shpadd = '/usr/bin/shpadd';
my $cs2cs = '/usr/bin/cs2cs';
my $ogrinfo = '/usr/bin/ogrinfo';

if(scalar (@ARGV) != 3){
    print "Incorrect number of arguments\n";
    usage;
}

my $epsg_in = $ARGV[0];
my $epsg_out = $ARGV[1];
my $in_file = $ARGV[2];

#validate parameters

if($epsg_in !~/^epsg:[0-9]+$/i){
    print "epsg_in is invalid\n";
    usage;
}

my $epsg_out_code = "0";
if($epsg_out!~/^epsg:([0-9]+)$/i){
    print "epsg_out is invalid\n";
    usage;
}
else{
    $epsg_out_code = $1;
}

if(! -f $in_file){
    print "$in_file is not a file\n";
    usage;
}

#determine shp file type

my @output = `$ogrinfo -so "$in_file" 2>&1`;
my $geometryType = undef;
foreach my $line (@output){
#    print "$line";
    if($line=~/\s\(Line String\)/){
        $geometryType = "linestring";
    }
    if($line=~/\s\(Point\)/){
        $geometryType = "point";
    }
    if($line=~/Unable to open datasource/){
        die "$in_file is not supported: $line\n";
    }
}

die "Unsupported geometry for $in_file. The only supported geometries are Line string and Point\n" if(!defined $geometryType);

#prepare destination file
my $file_base = $in_file;
$file_base=~s/\.shp$//i; #cut out the extension (if any)
my $out_file = $file_base;
$out_file.="_${epsg_out_code}"; #append the destination projection code

print "Going to save data as ${out_file}.[shp/shx/dbf]\n";
if($geometryType eq 'linestring'){
    print `$shpcreate "$out_file" arc 2>&1`;
}
if($geometryType eq 'point'){
    print `$shpcreate "$out_file" point 2>&1`;
}


print "Depending on your data size, this may take a while...\n";
@output = `$ogrinfo -al "$in_file" 2>&1`;
foreach my $line (@output){
    if($geometryType eq 'linestring'){
        if($line=~/^\s+LINESTRING \((.*)\)$/){
            my $linestring = $1;
#            print $linestring;
            my $projected = reproject($linestring);
           
            #add the line to the file
            print `$shpadd "$out_file" $projected 2>&1`;
           
        }
    }
    if($geometryType eq 'point'){
        if($line=~/^\s+POINT \((.*)\)$/){
            my $point = $1;
#            print $linestring;
            my $projected = reproject($point);
           
            #add the line to the file
            print `$shpadd "$out_file" $projected 2>&1`;
           
        }
    }
}

#we're almost done. Copy the dbf file unchanged (the order of the records is the same)
print "Copying dbf file...\n";
print `cp "$file_base.dbf" "$out_file.dbf" 2>&1`;



#take a line of coordinates and convert them. Return a string with the converted coordinates
sub reproject {
    my $string = shift;
    my $converted = "";
    my $coordinates = "";
    my @pairs = $string=~/([-0-9\.]+ [-0-9\.]+),?/g;
    foreach my $pair (@pairs){
#        print "DBG: $pair\n";
        my ($x, $y) = $pair=~/([-0-9\.]+) ([-0-9\.]+)/;
        $coordinates.= "$x $y\n";
    }
#    print "DBG: coordinates: $coordinates\n";
    #convert $coordinates (expensive, but we won't run into shell problems by converting everything at once)
    my $cmd = "echo '$coordinates' | $cs2cs +init=$epsg_in +to +init=$epsg_out -f '%0.6f' 2>&1";
#    print "DBG: $cmd";
    my @output = `$cmd`;
    foreach my $line (@output){
#        print $line;
        if($line=~/([-0-9\.]+)\s+([-0-9\.]+)/){
            next if ($line eq $output[-1]); #always skip the last line. It's not relevant to what we want
            my $x = $1;
            my $y = $2;
            $converted.=" $x $y";
        }
    }
   
#    print "DBG: converted:$converted\n";
    return $converted;
}


sub usage {
    print "Usage: $0 epsg_in epsg_out file.shp

epsg_in is the source EPSG projection code (e.g. epsg:31700)
epsg_out is the destination EPSG projection code (e.g. epsg:4326)
file.shp is the source shape file

Example: $0 epsg:31700 epsg:4326 streets.shp
";
    exit;
}