Changes at Google Maps

The good folks at Google Maps appear to have been busy. The UI for the zoom/pan control (GLargeMapControl) has been refreshed with a new skin. The process for selecting the position from which to view the neigborhood using the Street View feature is simplified – the draggable “person” is always present in the zoom control.

New Google Maps Interface
New Google Maps Interface

The entire Street View feature is now a Flash-based application, that takes over the map screen. Previously, the street panorama would be displayed in a Google Maps info window, and was implemented in DHTML.

The satellite imagery has also been replaced with higher-resolution pictures from the GeoEye Satellite, which was launched a couple of months ago. At least that’s the case in my neigborhood, and a couple of other spots I searched in California.

Even the Street View images have been updated – again, at least in my neigborhood. Check these out elizabethnelsonstudio .

Although the new zoom/pan control does not appear to be available to developers using the Google Maps API, the new satellite images are present by default. Hopefully this means that we’ll finally have a globally-consistent maximum zoom level!

Google Maps in OpenLaszlo

Recently, I wrote about Rmenuz, a site that embedded Google’s Flash maps in OpenLaszlo. The Google Flash Maps are written in ActionScript 3 (AS3), which requires Flash Player 9 or above. That means you wouldn’t be able to use them in OpenLaszlo 4.1 or below. I figured that with OpenLaszlo 4.2b3, which supports compiling to SWF9, it should be possible to embed an AS3 component. With some much-appreciated help from Henry, I got the following application working:

I wanted an example that:

  • Embedded Google maps in a clean, developer-friendly way.
  • Was full-featured (allowed a user to search for an address, and add a marker there).
  • Passed information (such as where to add a marker) from the OpenLaszlo application to the map component.
  • Passed information (such as the address of the marker that a user clicked) from the map to an object in the OpenLaszlo namespace.

Note that the Google Maps geocoding service is Flash-specific. Even though it passes XML back to the client, you need to use Google's Flash-based APIs to call it. I didn't want to do this; I wanted my example to perform the search in OpenLaszlo, and pass instructions to the map component. So I used Yahoo Maps' excellent geocoding service.

Here are the instructions on how to do this:

  1. Download OpenLaszlo 4.2b3 or later, and install.
  2. Get a Google Maps API key (free).
  3. Download the Google Maps API for Flash SDK, from Google's web site. I used version 1.7.
  4. Find the WEB-INF directory in your OpenLaszlo install, and create a directory called flexlib inside that. i.e. WEB-INF/flexlib/
  5. Unzip the Google Maps SDK, and locate the two SWC files in the lib directory. Take the Flash one (mine was called map_1_7a.swc) and copy it into WEB-INF/flexlib/.
  6. In your OpenLaszlo my-apps folder, create your lzx file (e.g. main.lzx), and write your application.
  7. When you compile the app via the browser, make sure that you remember to compile to SWF9. When you request the LZX file, append ?lzr=swf9 to the request. (e.g. main.lzx?lzr=swf9).

Here's my complete source:

<canvas proxied="false" width="468" height="530">

    <!-- antunkarlovac.com key -->
    <attribute name="gmapsKey"
               value="ABQIAAAAN0JyO4tW04-1OKNW7bg9gxSPySWqAfkZkuZG2U8jr6yyIuV3XBSrEn410_O9d9QPJh3dbWV85Qad8w" type="string" />

    <alert name="errorDialog" width="400" />

    <method name="handleMarkerClick" args="address">
        addressWin.writeAddress(address);
    </method>

    <script when="immediate"><!&#91;CDATA&#91;
        class FlashMapOL {
            #passthrough (toplevel: true) {
                import com.google.maps.*;
                import com.google.maps.controls.*;
                import com.google.maps.overlays.*;


                import flash.geom.*;
            }#

            var map:Map;

            function createMap() {
                map = new Map();
                map.addEventListener(MapEvent.MAP_READY, onMapReady);
                map.key = canvas.gmapsKey;
                map.setSize(new Point(450, 300));
                return map;
            }

            function onMapReady(event:MapEvent):void {
                map.addControl(new ZoomControl());
                map.addControl(new PositionControl());
                map.addControl(new MapTypeControl());
            }

            function addMarker(lat:Number, lon:Number, address:String):void {
                map.clearOverlays();
                var latLon:LatLng = new LatLng(lat, lon);
                var marker:Marker = new Marker(latLon);
                marker.addEventListener(MapMouseEvent.CLICK, function(e:MapMouseEvent):void {
                    canvas.handleMarkerClick(address);
                    map.openInfoWindow(latLon, new InfoWindowOptions({titleHTML: "<b>Search Result</b>", contentHTML: address}));
                });
                map.addOverlay(marker);
            }

            function centerAndZoom(lat:Number, lon:Number):void {
                var latLon:LatLng = new LatLng(lat, lon);
                this.map.setCenter(latLon, 14, MapType.NORMAL_MAP_TYPE);
            }

        }

        lz.mapFactory = new FlashMapOL();
        
        lz.map = lz.mapFactory.createMap();
    &#93;&#93;>
    </script>

    <dataset name="geoCode_ds"
             src="http://local.yahooapis.com/MapsService/V1/geocode?" type="http" request="false"/>

    <datapointer name="result_dp" 
                 xpath="geoCode_ds:/ResultSet/Result&#91;1&#93;" />

    <handler name="ondata" reference="geoCode_ds"><!&#91;CDATA&#91;
        var lat;
        var lon;
        var root_dp = geoCode_ds.getPointer();
        root_dp.selectChild();
        if (root_dp.getNodeName() != "Error") {
            lat = result_dp.xpathQuery("Latitude/text()") * 1;
            lon = result_dp.xpathQuery("Longitude/text()") * 1;
            var warn = result_dp.getNodeAttribute("warning");
            if (warn != undefined) {
                // Pass along any warnings (e.g. couldn't find address,
                // centering on city.
                errorDialog.setAttribute("text", warn);
                errorDialog.open();
            }
            // Get full address as a string
            var address = result_dp.xpathQuery("Address/text()")
                        + ",<br />" + result_dp.xpathQuery("City/text()")
                        + ", " + result_dp.xpathQuery("State/text()")
                        + " " + result_dp.xpathQuery("Zip/text()")
                        + "<br />" + result_dp.xpathQuery("Country/text()");
            lz.mapFactory.centerAndZoom(lat,lon);
            lz.mapFactory.addMarker(lat, lon, address);
        } else {
            // Failed to find address
            var msg = root_dp.xpathQuery("Message/text()");
            errorDialog.setAttribute("text", msg);
            errorDialog.open();
        }
    &#93;&#93;>
    </handler>

    <window name="mapWin" title="Map" width="468" height="369" 
            allowdrag="false">
        <passthrough>
            import flash.display.*;
        </passthrough>
        <handler name="oninit">
            var sprite:Sprite = this.content.sprite;
            sprite.addChildAt(lz.map, sprite.numChildren);
        </handler>

        <view name="buttons" bgcolor="0xeaeaea" width="100%"
              valign="bottom">
            <simplelayout axis="x" spacing="10" />
            <text valign="middle">Address Search:</text>
            <edittext name="addressInput" width="260" 
                      text="1221 Mariposa Street, San Francisco, CA" />
            <button>Search
                <handler name="onclick"><!&#91;CDATA&#91;
                    var qs = "appid=YD-9G7bey8_JXxQP6rxl.fBFGgCdNjoDMACQA--" 
                           + "&amp;amp;location=" + parent.addressInput.text 
                    geoCode_ds.setQueryString(qs);
                    geoCode_ds.doRequest();
                &#93;&#93;>
                </handler>
            </button>
        </view>
        
    </window>

    <window name="addressWin" title="Address" width="468" height="150" 
            y="379" allowdrag="false">
        <method name="writeAddress" args="address">
            this.txt.setAttribute("text", address);
        </method>

        <simplelayout axis="y" spacing="10" />

        <text width="100%" multiline="true" fontstyle="italic">
            Click the search button to search for an address. When you click
            one of the markers, the full address will be displayed in the
            OpenLaszlo text field below:
        </text>

        <!-- Address display area -->
        <text name="txt" width="100%" multiline="true" fontsize="14">
        </text>
    </window>

</canvas>

Note a few important points in the code:

  • There's AS3 code directly inside the script block in the canvas. You need to use the when="immediate" option in the script tag for this.
  • There's an OpenLaszlo compiler instruction that allows you to include libraries from the WEB-INF/flexlib/ directory:
    #passthrough (toplevel: true) {
    ...
    }#
  • There's another OpenLaszlo compiler instruction in the view where you want to instantiate the map (there's a new passthrough tag, and the map must be added procedurally - sprite.addChildAt().

Here's the complete source to my app, as a zip.

Rmenuz – OpenLaszlo and Google Maps

I looked at Google Map’s Flash API a while ago, trying to determine a way to integrate it into OpenLaszlo, but never had time to finish it. Today, I discovered Rmenuz, which is an OpenLaszlo application that lets you search for restaurants, and displays them using the Flash-based Google Maps.

Rmenuz Screenshot
Rmenuz Screenshot

I’m not sure what the status is of the site; it may still be under development. There’s a problem with its security certificate and I found a number of glitches with the application. You can try it at:

https://my.rmenuz.com/default.aspx

It can be hard to get to the map, since it’s only displayed if your search returns valid search results. To see the map, follow these steps:

  1. Click on the “Search by: CUISINE” window.
  2. Check “American Restaurants”.
  3. Select “Distance” 2.
  4. Enter the zip: 94107.
  5. Click go.