Twitter: raymondcamden


Address: Lafayette, LA, USA

Having trouble with too many map markers and CFMAP?

12-15-2009 7,880 views ColdFusion 18 Comments

About a few months ago a user contacted me with an odd issue with CFMAP. At random times the markers on the map refused to load. Actually they returned the rather odd error that the address didn't exist. Here is an example:

When I tested this myself I saw the same - even when I replaced the test addresses with ones I knew existed (like my own!). Unfortunately I wasn't able to help the user so I wished him luck and forgot about it.

A few days ago I got another email from someone experiencing the exact same issue. This time I decided to dig a bit deeper. I opened up Firebug and took a look at the requests. I noticed something odd. When the code made a geocoding request (ie, translate address so and so to longitude and latitude x and y), the result was different for addresses that failed to work. The result code was 620. After a quick Google search I found this list of geocode status results and bam - I found what 620 meant:

The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you're sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don't send the requests too quickly.

If you look at the code ColdFusion generates for the map you can see that all of the geocode requests are run immediately. This then makes sense why the error would occur. You can see this yourself with the following code:

view plain print about
1<cfset addresses = [
2"1802 San Pedro Avenue, Victoria BC",
3"1804 San Pedro Avenue, Victoria BC",
4"1810 San Pedro Avenue, Victoria BC",
5"1816 Feltham, Victoria BC",
6"1821 San Pedro Avenue, Victoria BC",
7"1823 San Pedro Avenue, Victoria BC",
8"1826 Feltham, Victoria BC",
9"1835 San Pedro Avenue, Victoria BC",
10"1837 San Pedro Avenue, Victoria BC",
11"1883 San Juan, Victoria BC",
12"1888 Feltham, Victoria BC",
13"1904 San Juan, Victoria BC",
14"1909 San Juan, Victoria BC",
15"1913 San Pedro Avenue, Victoria BC",
16"1921 San Fernando Pl, Victoria BC",
17"1972 San Rafael Cres, Victoria BC",
18"4090 San Capri Terrace, Victoria BC",
19"4098 San Capri Terrace, Victoria BC",
20"4101 San Capri Terrace, Victoria BC",
21"4110 Cortez Place, Victoria BC",
22"4120 Gordon Head Rd, Victoria BC",
23"4123 Cortez Ct, Victoria BC",
24"4151 San Mateo Place, Victoria BC"
25]
>

26
27<cfmap centeraddress="1802 San Pedro Avenue, Victoria BC" zoomlevel="15" height="600" width="800">
28 <cfloop index="m" array="#addresses#">
29 <cfmapitem address="#m#">
30 </cfloop>
31</cfmap>

Run this a few times and you will get errors on random addresses. Ok, so now that we know the bug involves geocoding, how can we fix it? Well one way would be to geocode the addresses ourselves in a separate process. I've got a CFC for that (Google Geocode) but it too will return an error if you try to geocode too many addresses at once.

The following code runs through the addresses and caches the result in the application scope. I'd probably recommend storing the geocodes in a database instead. Also note the use of sleep(). The number there (500 ms) was a pure guess but it seemed to work fine. If you wanted to be 100% rock solid, you could use a scheduled task to geocode addresses in the background. If an address fails one time it will (probably) get geocoded the next time.

Anyway, I hope this helps and I'm certainly glad I solved this mysery! (Note, in the code sample below I left off the array of addresses. They are the same as the code block above.)

view plain print about
1<cfif not structKeyExists(application, "longlat")>
2 <cfset application.longlat = {}>
3</cfif>
4
5<cfset geo = createObject("component", "googlegeocode")>
6<cfset key = "your google maps key goes here">
7<cfloop index="m" array="#addresses#">
8 <cfif not structKeyExists(application.longlat, m)>
9     <cfset result = geo.geocode(key,trim(m))>
10     <cfset sleep(500)>
11        <cfset application.longlat[m] = {longitude=result.longitude, latitude=result.latitude}>
12 </cfif>
13</cfloop>
14
15<cfmap centeraddress="1802 San Pedro Avenue, Victoria BC" zoomlevel="15" height="600" width="800">
16 <cfloop index="x" from="1" to="#arrayLen(addresses)#">
17 <cfset m = application.longlat[addresses[x]]>
18 <cfmapitem address="#m#">
19    <cfmapitem latitude="#m.latitude#" longitude="#m.longitude#" tip="#addresses[x]#">
20 </cfloop>
21</cfmap>

Related Blog Entries

18 Comments

  • Lola LB #
    Commented on 12-15-2009 at 9:04 PM
    Is storing the geocodes in a database really a good idea? Suppose you're setting up a user-based website where one of the primary features will be the ability to locate other users on the maps, say, all the members within a 25 mile range, or all 100 friends who are on that user-based website. That database table could get big very fast, I would think.
  • Commented on 12-15-2009 at 9:10 PM
    It is just a longitude and latitude. If you already store a full address, this is just two more fields. :)
  • Commented on 12-16-2009 at 12:47 AM
    Thanks, Ray. I knew you would know what to do.
    Genius answer - I didn't even realize the geocoding service was a separate function.

    I'll apply this right away to my project.

    Thanks again
  • Commented on 12-16-2009 at 3:55 AM
    Thank you. Good to know.
    Only thing I would change is a side where this would be processed. That is, I would move it from server to client side. With some nice progress bar ("Processing markers. Please wait...") it would be visually more attractive too.
    With increase of number of addresses, this would keep thread hanging for considerably long period.
  • Commented on 12-16-2009 at 4:18 AM
    Indeed, geocoding the addresses at first load and store them in a database is definitely the way to go.

    As for display performance on the client, I strongly recommend markers clustering. There is a nice JS library to do that (http://googlemapsapi.martinpearman.co.uk/readartic...).
    But I don't know if you can integrate it with cfmap (we don't use it...). It might be a great addition to cfmap.

    More info in this article :
    http://www.svennerberg.com/2009/01/handling-large-...
  • Commented on 12-16-2009 at 6:09 AM
    Marko: Why move it to the client side if you can do it server side behind the scenes? I mean obviously - if the user just entered the addresses, then you may need to, but if this is existing data, then I think it makes sense to do it server side as a background process.
  • Commented on 12-16-2009 at 6:25 AM
    Of course it strongly depends of scenario.
    From your example, I understood that is a case of bulk of addresses that need to be processed. As rule of thumb I always look for the way to save server resources as much as possible. One of "few" options is to move workload on client. Of course, having in mind, not to jeopardize their experience.
  • Commented on 12-16-2009 at 6:28 AM
    Ok, now thats an interesting argument. I'd try to push the work to the server and not the client. But yeah - it definitely depends.

    My only issue with the processing on the client is - you only want to do it one time. So your client needs to send the geocoded data back to the server so it can be persisted.
  • Commented on 12-16-2009 at 7:19 AM
    In my tests a few weeks ago I found between 9 and 11 map items where the max. Since the data was just test data and there was an admin to add new records I just added the geocoding during the add/edit process. Note that if you are going to to that you may also want to allow people to override the lat/long with their own. Google isn't perfect at geolocation and since you already are storing lat/long giving the user ability to be more precise is easy.
  • Commented on 12-16-2009 at 7:52 AM
    @Ray
    Sure. After processing (all) addresses, client can send coords back to server in single request....IF user stay long enough on that page. Otherwise, result can be sent back one-by-one.

    Pros and cons exist on both sides, but, god didn't gave us second generation of web (aka 2.0) for nothing :))
  • Commented on 12-16-2009 at 11:42 AM
    Thank you very much for this.
  • Mike Collins #
    Commented on 05-10-2010 at 9:51 AM
    I am losing my cfmapitems when I use the srollwheel to zoom. If I use the zoomcontrol + and - buttons , no issues.

    Anyone else seeing this?
  • Commented on 06-30-2010 at 10:00 AM
    Thank Raymond, I got on the right place after searching for the same issue. I would maintain the the Longitude and Latitudes in my database of addresses.
    But unfortunately I think riaforge has moved the Google Geocode API from the URL http://googlegeocode.riaforge.org/ to some where else or may be they have just removed it.

    Can I get it from else where
    Thanks
  • Commented on 06-30-2010 at 10:04 AM
    You want this: http://googlegeocoder3.riaforge.org/
  • Commented on 06-30-2010 at 2:50 PM
    ^ Exactly, I wanted it. thanks a lot for your help :)
  • Commented on 03-28-2011 at 12:24 PM
    Hi, I have managed to store my long and lats in a mysql db and call them to generate the cfmap items but still get this debug URL parameter error. Here is my code

    <cfquery name="qRecentEvents" datasource="funkadelicgrjk24">
    SELECT *
    FROM tblbookings
    WHERE tblbookings.bookingDate < now()
    AND tblbookings.bookinglatitude != ""
    AND tblbookings.bookinglongitude != ""
    AND tblbookings.bookingPostcode != ""
    ORDER BY tblbookings.bookingDate DESC
    LIMIT 0,20
    </cfquery>

    <cfajaximport params="#{googlemapkey='ABQIAAAADjCGEMrtI5RR9ca6jWaBRIIrxyUpiBF3OCcOJXxijgjehLExTdVid6CC1kqmSc2bcZFQYXpVADfg'}#">

    <cfmap name="gmap02"
    width="600" height="350"
    centeraddress="Birkenhead, Wirral, Merseyside, UK"
    doubleclickzoom="false"
    overview="false"
    scrollwheelzoom="true"
    showscale="false"
    tip="My Map"
    zoomlevel="9">

    <cfloop query="qRecentEvents">
    <cfmapitem name="#qRecentEvents.bookingID#"
    address="#qRecentEvents.bookinglatitude#, #qRecentEvents.bookinglongitude#"
    tip="#qRecentEvents.bookingAddressLine1#, #LSDateFormat(qRecentEvents.bookingDate, "ddd, mmmm dd, yyyy")#"/>
    </cfloop>

    </cfmap>
  • Commented on 03-28-2011 at 12:34 PM
    You are passing an address, which even though is a long/lat, your asking Google to geolocate it. Use the longtiude/latitude attributes instead. (See reference for cfmapitem.)
  • Commented on 03-28-2011 at 12:51 PM
    Superstar! Cheers Ray

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty