This group and the posts here have been very helpful to me, so now is my turn to help someone else. I modified the code of Pamela Fox for loading KML into a google map. I've put it into an actionscript class and made a few enhancements: 1) support setting opacity for polygons 2) support drawing polygon edges (or not) 3) fix KML namespace issue 4) draw custom marker icons (for placemarks) 5) easy to use class--just add it to your project and use it
You can just add GoogleMapKMLLoader.as to your project and use it to display your kml inside Google Maps for Flash. Feel free to change the package name from gov.noaa.kml to whatever you want, of course. First, here is an example of the code you'll need in your mxml file (mine is creatively called "main.mxml"): import gov.noaa.kml.GoogleMapKMLLoader; private var kmlLoader : GoogleMapKMLLoader = null; private var zoomControl : ZoomControl; private var mapTypeControl : MapTypeControl; private var kml : String = null; private var kmlLoader : GoogleMapKMLLoader = null; private function onMapInitialize(event:Event) : void { var mapOptions : MapOptions = new MapOptions(); mapOptions.zoom = 5; mapOptions.center = new LatLng(38, -100); mapOptions.mapType = MapType.HYBRID_MAP_TYPE; map.setInitOptions(mapOptions); } private function onMapReady(event:Event) : void { zoomControl = new ZoomControl(); mapTypeControl = new MapTypeControl(); chkShowMapControls_clickHandler(null); map.addEventListener(MapMoveEvent.MOVE_END, onMapMoveEnd); map.openInfoWindow(map.getCenter(), new InfoWindowOptions({title: "FIM MAPS", content: "Select the FIM data you wish to view on the right and then press the 'Refresh Map' button."})); } //if the user pans the map a significant amount, load new kml private function onMapMoveEnd(event:MapMoveEvent) : void { var center:LatLng = map.getCenter(); var changeInLat : Number = Math.abs(center.lat() - centerLat); var changeInLon : Number = Math.abs(center.lng() - centerLon); var change : Number = changeInLat + changeInLon; var MAX_GRID_LEVEL : Number = 9; var currentGridLevel : Number = cbFimGridLevels.selectedIndex + 5; var maxChangeWithoutChangingView : Number = (MAX_GRID_LEVEL - currentGridLevel + 1) * 2; if (change > maxChangeWithoutChangingView) btnRefresh_clickHandler(null); } I have a web service I call that returns the KML as a string. Here is the function that handles that web service call (on success): public function handleGetKMLSuccess(event:ResultEvent):void { kml = event.result.toString(); kmlLoader = new GoogleMapKMLLoader(map, "FIM Map"); kmlLoader.showPolygonEdges = true; kmlLoader.opacity = 0.5; //50% opacity kmlLoader.loadKML(kml); } Here's my Map object from the .mxml: <maps:Map xmlns:maps="com.google.maps.*" id="map" mapevent_mappreinitialize="onMapInitialize(event)" mapevent_mapready="onMapReady(event)" width="100%" height="100%" key="<your google maps key goes here"/> Below is my GoogleMapKMLLoader class. This class allows you to display your KML within the map. Note that for my KmlPoints, I display a custom "plane-icon.png" image. You could do your own custom marker icon in your projects. Just swap out planeIcon for your own icon(s). If you don't need custom marker icons, then delete the references to plane-icon.png and the corresponding imgCls variable. Another note. I use this class for drawing polygons in Google Maps, and for that purpose, I have two public variables that can be set before calling the main loadKML() method: public var showPolygonEdges : Boolean = true; public var opacity : Number = -1; If you set showPolygonEdges=true, then the edges of polygons are drawn (instead of simply the fill color). If you set the opacity to some number (e.g. 0.5 for 50% opacity), then this opacity setting overrides whatever is in your kml. For an example of what my kml looks like, see the getSampleKML() function at the end of the class file. Well, enough said. Here's the GoogleMapKMLLoader class: package gov.noaa.kml { import com.google.maps.LatLng; import com.google.maps.LatLngBounds; import com.google.maps.Map; import com.google.maps.extras.xmlparsers.kml.*; import com.google.maps.overlays.GroundOverlay; import com.google.maps.overlays.Marker; import com.google.maps.overlays.MarkerOptions; import com.google.maps.overlays.Polygon; import com.google.maps.overlays.PolygonOptions; import com.google.maps.overlays.Polyline; import com.google.maps.styles.FillStyle; import com.google.maps.styles.StrokeStyle; import flash.display.Loader; import flash.events.Event; import flash.net.URLRequest; import mx.core.BitmapAsset; /** * This class is based on the open source KMLParser.mxml written by Google employee, Pamela Fox. * Jeff Smith adapted it into this class (OOP) form and added support for alternative kml namespaces, setting the opacity, * turning on/off polygon edges, adding custom marker icons (for placemarks). * Required Lib: GoogleMapsAPIUtilityLibrary_04262009.swc */ public class GoogleMapKMLLoader { private var map : Map; private var markerTooltipMsg : String; private var kmlObj : Object; private const KML_NAMESPACE : String = "<kml xmlns='http:// earth.google.com/kml/2.2' xmlns:gx='http://www.google.com/kml/ext/2.2' xmlns:atom='http://www.w3.org/2005/Atom'>"; public var showPolygonEdges : Boolean = true; public var opacity : Number = -1; [Embed(source="assets/plane-icon.png")] [Bindable]public var imgCls : Class; public function GoogleMapKMLLoader(map : Map, markerTooltipMsg : String) { this.map = map; this.markerTooltipMsg = markerTooltipMsg; } /** * Swap out this: <kml xmlns='http://earth.google.com/kml/2.1'> * For this: <kml xmlns='http://earth.google.com/kml/2.2' xmlns:gx='http://www.google.com/kml/ext/2.2' xmlns:atom='http:// www.w3.org/2005/Atom'> * Without this namespace switch, Kml22 class returns kml.feature=null */ private function fixKMLNamespace(kmlStr : String) : String { var startPos : int = kmlStr.indexOf("<kml"); var endPos : int = kmlStr.indexOf(">", startPos); var fixedKmlStr : String = kmlStr.substring(0, startPos) + KML_NAMESPACE + kmlStr.substring(endPos+1, kmlStr.length); return(fixedKmlStr); } public function loadKML(kmlStr : String):void { map.clearOverlays(); kmlObj = new Object(); var fixedKmlStr : String = fixKMLNamespace(kmlStr); var kml:Kml22 = new Kml22(fixedKmlStr); var rootFeature:Feature = kml.feature; kmlObj.name = rootFeature.name; kmlObj.mapObjs = new Array(); kmlObj.bounds = new LatLngBounds(); if (canContainFeatures(rootFeature)) kmlObj.children = getChildrenFeatures(Container(rootFeature)); else associateWithMapObject(kmlObj, rootFeature); } private function getChildrenFeatures(container:Container):Array { var childrenFeatures:Array = new Array(); for (var i:Number = 0; i < container.features.length; i++) { var feature:Feature = container.features[i]; var childObj:Object = new Object(); childObj.mapObjs = new Array(); childObj.name = feature.name; if (childObj.name == null) childObj.name = getAlternateName(feature); if (canContainFeatures(feature)) childObj.children = getChildrenFeatures(Container(feature)); else associateWithMapObject(childObj, feature); childrenFeatures.push(childObj); } return childrenFeatures; } private function getAlternateName(feature:Feature):String { if (feature is Folder) return "Unnamed Folder"; else if (feature is Document) return "Unnamed Document"; else if (feature is Placemark) { var placemark:Placemark = Placemark(feature); if (placemark.geometry != null) { if (placemark.geometry is KmlPoint) return "Unnamed Point"; else if (placemark.geometry is LineString) return "Unnamed Linestring"; else if (placemark.geometry is LinearRing) return "Unnamed LinearRing"; else if (placemark.geometry is KmlPolygon) return "Unnamed Polygon"; } return "Unnamed Placemark"; } else if (feature is com.google.maps.extras.xmlparsers.kml.GroundOverlay::KmlGroundOverlay) return "Unnamed GroundOverlay"; return "Unnamed Feature"; } private function associateWithMapObject(obj:Object, feature:Feature):void { // at this point it can either be a placemark or a groundoverlay if (feature is Placemark) { var placemark:Placemark = Placemark(feature); if (placemark.geometry != null) { var placemarkDesc : String = placemark.description; placemarkDesc = placemarkDesc.replace("<br>", "\n").replace("<br/>", "\n"); associateGeometryWithMapObject(obj, placemarkDesc, placemark.geometry, placemark.styleUrl); } } else if (feature is KmlGroundOverlay) { var groundOverlay:KmlGroundOverlay = KmlGroundOverlay(feature); var latLngBounds:LatLngBounds = new LatLngBounds(new LatLng(groundOverlay.latLonBox.south,groundOverlay.latLonBox.west), new LatLng(groundOverlay.latLonBox.north,groundOverlay.latLonBox.east)); updateLatLngBounds(obj, latLngBounds); var testLoader:Loader = new Loader(); var urlRequest:URLRequest = new URLRequest(groundOverlay.icon.href); testLoader.contentLoaderInfo.addEventListener ( Event.COMPLETE, function(e:Event):void { obj.mapObject = new com.google.maps.overlays.GroundOverlay::GroundOverlay(testLoader, latLngBounds); map.addOverlay(obj.mapObject); }); testLoader.load(urlRequest); } } private function getRGBFromKMLStyle(styleURL : String) : Number { if (styleURL == null) return(uint("0x008080")); else { var red : String = styleURL.substring(6, 8); var green : String = styleURL.substring(4, 6); var blue : String = styleURL.substring(2, 4); var fillColor : Number = uint("0x" + red + green + blue); return(fillColor); } } private function associateGeometryWithMapObject(obj:Object, placemarkDesc: String, geometry:Geometry, styleURL : String):void { var multiGeometry : MultiGeometry = null; var isMultigeometryOfOnePolygon : Boolean = false; if (geometry is MultiGeometry) { multiGeometry = MultiGeometry(geometry); if (multiGeometry.geometries.length == 1) isMultigeometryOfOnePolygon = true; } var polyline:Polyline; var fillAlpha : Number; if (styleURL != null && styleURL.charAt(0) == '#') { styleURL = styleURL.substr(1, styleURL.length); fillAlpha = parseInt(styleURL.substr(0, 2), 16) / 256.0; } else { styleURL = "FF0000a1"; fillAlpha = 0.75; } if (opacity >= 0) //if opacity level set, then use it (overriding what is in the KML) fillAlpha = opacity; var thisFillStyle: FillStyle = new FillStyle(); thisFillStyle.alpha = fillAlpha; thisFillStyle.color = getRGBFromKMLStyle(styleURL); var thisStrokeStyle : StrokeStyle = new StrokeStyle(); if (showPolygonEdges) thisStrokeStyle.alpha = 0.8; else thisStrokeStyle.alpha = 0.0; thisStrokeStyle.color = 0xffffff; if (geometry is KmlPoint) { var point:KmlPoint = KmlPoint(geometry); var latlng:LatLng = new LatLng(point.coordinates.coordsList[0].lat, point.coordinates.coordsList[0].lon); var markerOptions : MarkerOptions = new MarkerOptions(); var planeIcon : BitmapAsset = new imgCls() as BitmapAsset; markerOptions.icon = planeIcon; markerOptions.tooltip = markerTooltipMsg + latlng; markerOptions.iconAlignment = MarkerOptions.ALIGN_HORIZONTAL_CENTER; markerOptions.hasShadow = true; markerOptions.radius = 12; var marker: Marker = new Marker(latlng, markerOptions); obj.mapObjs.push(marker); updateLatLngBounds(obj, new LatLngBounds(latlng, latlng)); map.addOverlay(obj.mapObjs[obj.mapObjs.length -1]); } else if (geometry is LineString) { var lineString:LineString = LineString(geometry); polyline = new Polyline(getCoordinatesLatLngs(lineString.coordinates)); obj.mapObjs.push(polyline); updateLatLngBounds(obj, polyline.getLatLngBounds()); obj.center = polyline.getLatLngBounds().getCenter(); obj.bounds = polyline.getLatLngBounds(); map.addOverlay(polyline); } else if (geometry is LinearRing) { var linearRing:LinearRing = LinearRing(geometry); polyline = new Polyline(getCoordinatesLatLngs(linearRing.coordinates)); obj.mapObjs.push(polyline); updateLatLngBounds(obj, polyline.getLatLngBounds()); map.addOverlay(polyline); } else if (geometry is KmlPolygon || isMultigeometryOfOnePolygon) { var kmlPolygon : KmlPolygon; if (isMultigeometryOfOnePolygon) { multiGeometry = MultiGeometry(geometry); kmlPolygon = multiGeometry.geometries[0]; } else kmlPolygon = KmlPolygon(geometry); var polygonOptions : PolygonOptions = new PolygonOptions({fillStyle:thisFillStyle, strokeStyle:thisStrokeStyle, tooltip:placemarkDesc}); var polygon : com.google.maps.overlays.Polygon = new com.google.maps.overlays.Polygon(getCoordinatesLatLngs(kmlPolygon.outerBoundaryIs.linearRing.coordinates), polygonOptions); obj.mapObjs.push(polygon); updateLatLngBounds(obj, polygon.getLatLngBounds()); map.addOverlay(polygon); } else if (geometry is MultiGeometry) { multiGeometry = MultiGeometry(geometry); for (var i:uint = 0; i < multiGeometry.geometries.length; i++) { associateGeometryWithMapObject(obj, placemarkDesc, multiGeometry.geometries[i], styleURL); } } } private function getCoordinatesLatLngs(coordinates:Coordinates):Array { var latlngs:Array = new Array(); for (var i:Number = 0; i < coordinates.coordsList.length; i++) { var coordinate:Object = coordinates.coordsList[i]; latlngs.push(new LatLng(Number(coordinate.lat), Number(coordinate.lon))); } return latlngs; } private function updateLatLngBounds(obj:Object, bounds:LatLngBounds):void { if (obj.bounds) obj.bounds.union(bounds); else obj.bounds = bounds; kmlObj.bounds.union(bounds); } private function canContainFeatures(feature:Feature):Boolean { return (feature is Container); } //this function is useful for debugging. It is not currently called. public function getSampleKML() : String { return("<?xml version='1.0' encoding='UTF-8'?> " + "<kml xmlns='http://earth.google.com/kml/2.1'> " + "<Document> " + "<name>FIM Earth Grid Level 5</name> " + "<Style id='7F008000'><LineStyle><color>60FFFFFF</ color><width>1.0</width></LineStyle><PolyStyle><color>7F008000</ color></PolyStyle></Style> " + "<Placemark> " + " <name>FIM Grid Cell 1</name> " + " <description>TEMP=295.9104></description> " + " <open>false</open> " + " <styleUrl>#7F008000</styleUrl> " + " <MultiGeometry> " + " <Polygon> " + " <altitudeMode>absolute</altitudeMode> " + " <outerBoundaryIs> " + " <LinearRing> " + " <coordinates>-98,41.953,35000 -98,40.723,35000 -96.46,39.997,35000 -94.883,40.681,35000 -94.826,41.911,35000 -96.393,42.659,35000 -98,41.953,35000</coordinates> " + " </LinearRing> " + " </outerBoundaryIs> " + " </Polygon> " + " </MultiGeometry> " + "</Placemark> " + "</Document> " + "</kml>"); } } } -- You received this message because you are subscribed to the Google Groups "Google Maps API For Flash" group. To post to this group, send email to google-maps-api-for-fl...@googlegroups.com. To unsubscribe from this group, send email to google-maps-api-for-flash+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/google-maps-api-for-flash?hl=en.