• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 424
  • Last Modified:

Application to identify city from GPS coordinates

Hi.  I am looking for an application which can identify by name a city from long/lat.  I need to feed a file of records ...

15|1|0|09/11/2011 00:00:11|Y|51440|51440|34046999|-118457499|62|6519|22012|0|
15|1|0|09/11/2011 00:00:26|Y|51832|51832|34094666|-118347166|23|6312|18841|0|
15|1|0|09/11/2011 00:00:51|Y|51772|51772|34060999|-118435166|30|6626|21081|0|
15|1|0|09/11/2011 00:01:01|Y|51128|51128|34048166|-118459833|86|6245|19869|0|

..into it and be able to extract only those records in a specific California city.  An application or even an API to build such an application around.  Access to the internet during processing is a given.
0
michaelheffernan
Asked:
michaelheffernan
  • 9
  • 5
1 Solution
 
Ray PaseurCommented:
The thing you want is called a "reverse geocoder" and it is a relatively new invention.  Where did these pipe-delimited strings come from?  Can you please tell us what is in each sub-string?  Thanks.
0
 
michaelheffernanAuthor Commented:
Hello. Thank you for responding.
The fields are:
type small_int
tds_nbr int
dt_tm datetime
fl_id char
call_nbr int
info1 int
info2 int
info3 int
info4 int
vh_nbr smallint
dr_id int
user_id smallint

In this instance (an export from a vehicle dispatch system), info2 and info3 are the long/lat of an event.

"Reverse decoders" are new?  I figured Google et al have been doing this for years, and that perhaps "they" have an appropriate webservice.  However, I will need to run several thousand of these records thru this black box monthly to identify that event occurring in that city (either great circle or box-defined - it won't be perfect, I know), and suspect a web service would be too slow.  I'd rather a localized DB to run these against.


0
 
Ray PaseurCommented:
I have to leave for an appointment now so I cannot write the code sample that I would normally write - at least not until tomorrow.  But have a look at this:
http://code.google.com/apis/maps/documentation/geocoding/#ReverseGeocoding
http://code.google.com/apis/maps/documentation/geocoding/#Limits

Sorry - gotta run.  But I expect that with a little creative design you can get this to work below the Google radar line of 2,500 calls.
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
michaelheffernanAuthor Commented:
Thanks. I will look at 'em.  I also found an API called MapPoint which might do the trick...
0
 
michaelheffernanAuthor Commented:
Well, a little research indicates something like a minute to more than two minutes per lat/lng query!  Methinks I need to stick to a localized DB...
0
 
Ray PaseurCommented:
My results suggest about a tenth of a second per geocoder query.  Here is my test script.
<?php // RAY_temp_michaelheffernan.php
error_reporting(E_ALL);
echo "<pre>";

// FROM EE: http://www.experts-exchange.com/Database/GIS_and_GPS/Q_27304002.html?cid=1572#a36530622
// FROM GOOGLE: http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=true_or_false

// TEST DATA
$thing = <<<THING
15|1|0|09/11/2011 00:00:11|Y|51440|51440|34046999|-118457499|62|6519|22012|0|
15|1|0|09/11/2011 00:00:26|Y|51832|51832|34094666|-118347166|23|6312|18841|0|
15|1|0|09/11/2011 00:00:51|Y|51772|51772|34060999|-118435166|30|6626|21081|0|
15|1|0|09/11/2011 00:01:01|Y|51128|51128|34048166|-118459833|86|6245|19869|0|
THING;

// THE DESCRIPTION OF THE TEST DATA APPEARS TO HAVE OMITTED A FIELD BEFORE THE DATETIME STRING
// type small_int
// tds_nbr int
// dt_tm datetime
// fl_id char
// call_nbr int
// info1 int
// info2 int
// info3 int
// info4 int
// vh_nbr smallint
// dr_id int
// user_id smallint
// In this instance (an export from a vehicle dispatch system), info2 and info3 are the long/lat of an event.

// THE LOCATION OF THE WEB SERVICE
$url = 'http://maps.googleapis.com/maps/api/geocode/json?sensor=false&latlng=';

// START A TIMER TO SEE HOW LONG THIS PROCESS TAKES
$stw = new StopWatch;
$stw->start('GMAP');

// PROCESS THE FOUR GEOCODES THROUGH THE REVERSE GEOCODER
$dlm = '|';
$num = 0;
$arr = explode(PHP_EOL, $thing);
foreach ($arr as $str)
{
    $num++;
    $sub = explode($dlm, $str);
    $lat = (string)$sub[7] / 1000000.0;
    $lon = (string)$sub[8] / 1000000.0;
    $geo = "$lat,$lon";
    $req = $url . $geo;
    var_dump($req);
    $res = file_get_contents($req);
    $obj = json_decode($res);
    var_dump($obj);
}

// SHOW HOW LONG THIS TOOK
echo PHP_EOL;
echo "FINISHED $num GEOCODER CALLS.  HERE IS THE TIMER DATA:";
echo PHP_EOL;
echo $stw->readout('GMAP');
echo " MILLISECONDS";



// A CLASS TO TIME PART OF THIS SCRIPT
class StopWatch
{
    protected $a; // START TIME
    protected $s; // STATUS - IF RUNNING
    protected $z; // STOP TIME

    public function __construct()
    {
        $this->a = array();
        $this->s = array();
        $this->z = array();
    }

    // A METHOD TO REMOVE A TIMER
    public function reset($name='TIMER')
    {
        // RESET ALL TIMERS
        if ($name == 'TIMER')
        {
            $this->__construct();
        }
        else
        {
            unset($this->a[$name]);
            unset($this->s[$name]);
            unset($this->z[$name]);
        }
    }

    // A METHOD TO CAPTURE THE START TIME
    public function start($name='TIMER')
    {
        $this->a[$name] = microtime(TRUE);
        $this->z[$name] = $this->a[$name];
        $this->s[$name] = 'RUNNING';
    }

    // A METHOD TO CAPTURE THE END TIME
    public function stop($name='TIMER')
    {
        $ret = NULL;

        // STOP ALL THE TIMERS
        if ($name == 'TIMER')
        {
            foreach ($this->a as $name => $start_time)
            {
                // IF THIS TIMER IS STILL RUNNING, STOP IT
                if ($this->s[$name])
                {
                    $this->s[$name] = FALSE;
                    $this->z[$name] = microtime(TRUE);
                }
            }
        }

        // STOP ONLY ONE OF THE TIMERS
        else
        {
            if ($this->s[$name])
            {
                $this->s[$name] = FALSE;
                $this->z[$name] = microtime(TRUE);
            }
            else
            {
                $ret .= "ERROR: CALL TO STOP() METHOD FOR '$name' IS NOT RUNNING";
            }
        }

        // RETURN AN ERROR MESSAGE, IF ANY
        return $ret;
    }

    // A METHOD TO READ OUT THE TIMER(S)
    public function readout($name='TIMER', $dec=3, $m=1000, $eol=NULL)
    {
        $str = NULL;

        // GET READOUTS FOR ALL THE TIMERS
        if ($name == 'TIMER')
        {
            foreach ($this->a as $name => $start_time)
            {
                $str .= $name;

                // IF THIS TIMER IS STILL RUNNING UPDATE THE END TIME
                if ($this->s[$name])
                {
                    $this->z[$name] = microtime(TRUE);
                    $str .= " RUNNING ";
                }
                else
                {
                    $str .= " STOPPED ";
                }

                // RETURN A DISPLAY STRING
                $lapse_time = $this->z[$name] - $start_time;
                $lapse_msec = $lapse_time * $m;
                $lapse_echo = number_format($lapse_msec, $dec);
                $str .= " $lapse_echo";
                $str .= $eol;
            }
            return $str;
        }

        // GET A READOUT FOR ONLY ONE TIMER
        else
        {
            $str .= $name;

            // IF THIS TIME IS STILL RUNNING, UPDATE THE END TIME
            if ($this->s[$name])
            {
                $this->z[$name] = microtime(TRUE);
                $str .= " RUNNING ";
            }
            else
            {
                $str .= " STOPPED ";
            }


            // RETURN A DISPLAY STRING
            $lapse_time = $this->z[$name] - $this->a[$name];
            $lapse_msec = $lapse_time * $m;
            $lapse_echo = number_format($lapse_msec, $dec);
            $str .= " $lapse_echo";
            $str .= $eol;
            return $str;
        }
    }
}

Open in new window

0
 
michaelheffernanAuthor Commented:
Hmm...lemme give this a good look...TY
0
 
michaelheffernanAuthor Commented:
// THE DESCRIPTION OF THE TEST DATA APPEARS TO HAVE OMITTED A FIELD BEFORE THE DATETIME STRING

Oops, yes, I did.  Type, status, tds_nbr,datetime.... sorry.
0
 
michaelheffernanAuthor Commented:
Well, we ran a handful of records thru Google; some of the times were indeed in the < 2 sec range.  However, there were several queries that stalled for far longer:

1. 1.23 seconds
2. 1.18 seconds
3. 1.43 seconds
4. 119.28 seconds
5. 2.43 seconds
6. 9.28 seconds
7. 1.29 seconds
8. 3.48 seconds
9. 2.11 seconds
10. 54.31 seconds

I have to suspect there are other factors (like network or server load or whatever) responsible for the wild swings.

I'm going to invest in the MapPoint solution ($500 or so is reasonable) to have local datamap access.  But I thank you for your help.
0
 
michaelheffernanAuthor Commented:
query code was particularly useful.
0
 
Ray PaseurCommented:
Interesting about the times you're getting from Google.  Possibly they throttle the API to prevent overloading?  I ran my script several times and never had it run longer than 0.4 seconds for the four separate calls.

I think if this were my app, I might keep a data base of lat/lon pairs and the responses (or parts of responses) that matter. That way you can check your data base first before you have to go to an outside service.  It will make the app more responsive if you do not have the external calls.  But maybe MapPoint will obviate the need for that.

Best regards, ~Ray
0
 
michaelheffernanAuthor Commented:
Ah, that is a good idea.  I can run against Google in the event MapPoint can't identify a pair.  I cannot imagine how Google would react if I ran 4500 records thru it, tho.  Not well, I'd imagine.  I suspect you're correct in speculating that Google may throttle under a variety of conditions.
Thank you again.
0
 
Ray PaseurCommented:
There are 2,500 calls permitted per day.  If your script sleeps for a second between calls, you can do 2,500 now and 2,000 tomorrow.  Neither session will take longer than one hour (3,600 seconds per hour).  Just a thought.
0
 
michaelheffernanAuthor Commented:
Makes sense.  I am still concerned with the latency I did see.  Thanks.
0

Featured Post

Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

  • 9
  • 5
Tackle projects and never again get stuck behind a technical roadblock.
Join Now