GmapEZ

2010 February 6
by Adam Estrada

Using GMapEZ

I have been using GMapEZ for a few years now and it continues to impress me by how easy it is to use. The GMapEZ  script completely eliminates the need for any javascript programming which in my opinion is a really good thing. I have used it in the past to add pushpins that are returned form any number of data sources. I like classic ASP so I’ve returned data from an ASP RecordSet or now a days from a .NET DataReader. At any rate, you simply have to read data from the DB and then write out the HTML to represent the structure needed to put pushpins on your map. Here is a C# example of what I am talking about.

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public partial class Pages_mapPanel : System.Web.UI.UserControl
{
    #region Page Props
 
    public String defaultGmapPage = "http://maps.google.com/maps";
    public String defaultPage = ConfigurationSettings.AppSettings["defaultPage"];
 
    #endregion
 
    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            string mapPlaceHolder = this.loadMapData();
            this.ctlPlaceHolder.Controls.Add(new LiteralControl(mapPlaceHolder)); 
        }
        catch (Exception ex)
        {
            throw (ex);
        }
    }
 
    #region loadMapData()
    public String loadMapData()
    {
        try
        {
            OracleConnection conn = new OracleConnection();
            String mainConn = ConfigurationManager.ConnectionStrings["MainConnStr"].ConnectionString;
            conn.ConnectionString = mainConn;
            conn.Open();
            OracleCommand cmd = new OracleCommand();
            cmd.CommandType = CommandType.Text;
 
            String query = "SELECT * FROM MYTABLE ORDER BY DATEIN DESC";
            cmd.CommandText = query;
            cmd.Connection = conn;
 
            cmd.ExecuteReader();
            OracleDataReader dr = cmd.ExecuteReader();
            StringBuilder map = new StringBuilder();
            QueryString qs = new QueryString();
 
            while (dr.Read())
            {
                qs.Add("fid", "ms");
                qs.Add("id", dr["ID"].ToString());
 
                    map.Append("<a id=\"" + dr["ID"].ToString() + "\" href=\"" + defaultGmapPage + "?ll=" + dr["LATITUDE"].ToString() + "," + dr["LONGITUDE"].ToString() + "&amp;t=k&amp;hl=en\">GREEN MINI</a>");
                    map.Append("<div title=\"Event\"><font size=\"1\" face=\"Arial, Helvetica, sans-serif\">");
                    map.Append("<strong>Name:</strong> " + dr["NAME"].ToString() + "<br />");
                    map.Append("<strong>Type:</strong> " + dr["TYPE"].ToString() + "<br />");
                    map.Append("<strong>Description:</strong> " + dr["DESC"].ToString() + "<br />");
                    map.Append("<a href=\"" + defaultPage + qs + "\">Go To Main Page...</a>");
                    map.Append("</font></div>");
                    map.Append("<div title=\"Dates\"><font size=\"1\" face=\"Arial, Helvetica, sans-serif\">");
                    map.Append("<strong>Date In:</strong> " + dr["DATEIN"].ToString() + "<br />");
                    map.Append("<strong>Date Out:</strong> " + dr["DATEOUT"].ToString() + "<br />");
                    map.Append("<a href=\"" + defaultPage + qs + "\">Go To Main Page...</a>");
                    map.Append("</font></div>");
                }
            }
 
            conn.Close();
            return map.ToString();
        }
        catch (Exception ex)
        {
            Zekiah.Logging.LoggingManager.WebApplication.Error("A Fatal Error has occured in the Map Control", ex);
            redirectToFailure();
            return null;
        }
    }
    #endregion
 
	#region Failure Notice 
    private void redirectToFailure()
    {
        QueryString qs = new QueryString();
        qs.Add("fid", "ERROR");
        Response.Redirect(defaultPage + qs, false);
    }    
    #endregion 
}

Please note that I am reading data from an Oracle Database and simply returning it as a properly formatted String in HTML format. Each pushpin also has two tabs which can hold extra data including another URL. For more information on what you can do with GMapEZ, check out the examples on the hosted site. You’ll also note that I used a class written by Bobby ReRosa called “QueryString”. What a freaking time saver this thing is! Thanks Bobby…I actually want to write more on how to use it in another blog post so look for that one too.

Oracle Spatial and USER_SDO_GEOM_METADATA

2010 January 15
by Adam Estrada

Oracle is a very powerful database but at the same time VERY difficult to use. I’m guessing that’s why Oracle DBA’s get all the money and women…hehe. Oracle Spatial is also an incredibly powerful tool, if you know how to use it. I’ve talked before about how to get your spatial data in to the Database, but what happens if the database doesn’t like the data? See, in Oracle you must have an SDO_GEOMETRY type to actually perform spatial queries on your data. What they don’t make so apparent is the fact that you must also Insert a record in to the USER_SDO_GEOM_METADATA table (they call it a view) and also create a Spatial Index to perform the query. SHP2SDO will create the Update/Insert statement for USER_SDO_GEOM_METADATA but if you already Inserted the data and don’t have the original you can use the following to Insert in to your table

1
2
3
4
5
6
7
8
9
INSERT INTO USER_SDO_GEOM_METADATA
VALUES(
'EVENTS','GEOM',
    MDSYS.SDO_DIM_ARRAY(   
    MDSYS.SDO_DIM_ELEMENT('X', -180, 180, 0.000000005),
    MDSYS.SDO_DIM_ELEMENT('Y', -90, 90, 0.000000005)
     ),
  4326   -- SRID (reserved for future Spatial releases)
);

Once you’ve Inserted that record you can look at it using the following Query:

1
2
 
SELECT * FROM USER_SDO_GEOM_METADATA

The Update query is simple…

1
2
3
4
5
6
7
UPDATE EVENTS S set S.GEOM.SDO_SRID = 4326
 
UPDATE USER_SDO_GEOM_METADATA SET SRID = 4326 WHERE TABLE_NAME='EVENTS'
 
UPDATE USER_SDO_GEOM_METADATA SET DIMINFO = 
MDSYS.SDO_DIM_ARRAY(MDSYS.SDO_DIM_ELEMENT('X',-180,180,0.000000005),
MDSYS.SDO_DIM_ELEMENT('Y',-90,90,0.000000005)) WHERE TABLE_NAME = 'EVENTS'

If you want to make sure your geometries are valid, you may use Oracle’s ST_IsValid() command.

1
2
 
SELECT s.GEOM.st_isvalid() FROM EVENTS s

Finally, do you want to generate a GEOM column from latitude/longitude fields?

1
2
3
4
5
6
UPDATE EVENTS
SET GEOM = MDSYS.SDO_GEOMETRY(
2001, -- 3-dimensional point or use 2001 for 2-dimensional point
NULL,
MDSYS.SDO_POINT_TYPE(EVENTS.LON, EVENTS.LAT, NULL), -- X,Y,Z
NULL, NULL );

Have fun!

GIS for the “Have Nots”

2009 December 17
by Adam Estrada

A colleague of mine, Marshall Worthey, gave this presentation at GIS in the Rockies earlier this year. Last night, I did it at a GeoDC event at FortiusOne.

For more information on embedding maps from Geocommons in to Wordpress, check out this previous post of mine.

More on ESRI Flex and Config.xml

2009 December 15

More often than not, when building a Flex application that includes a URL that references a map service, you’ll have to swap out that URL from your development environment to production when you go live. The following script/technique is intended to alleviate having to completely rebuild your application during deployment.

Take a look at the following mxml file and note that there are 3 things in particular to note.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
< ?xml version="1.0" encoding="utf-8"?>
<mx :Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:esri="http://www.esri.com/2008/ags"
    layout="absolute"
    initialize="init()"
    styleName="plain"
    >
  </mx><mx :Script>
    < ![CDATA[
    	import mx.collections.XMLListCollection;
    	import mx.collections.ArrayCollection;
        	import com.esri.ags.Graphic;
        	import com.esri.ags.events.MapMouseEvent;        	
        	import com.esri.ags.geometry.MapPoint;
        	import com.esri.ags.geometry.Extent;
            import com.esri.ags.events.ExtentEvent;
            import mx.rpc.events.FaultEvent;
        	import mx.rpc.events.ResultEvent;
            import mx.controls.Alert;
            import flash.external.*;
            import mx.events.FlexEvent;
            import com.esri.ags.layers.TiledMapServiceLayer;
            import mx.controls.Button;
            import mx.controls.Text;
            import com.esri.ags.layers.ArcGISDynamicMapServiceLayer;
            import mx.rpc.http.HTTPService;
 
            private function initXML():void
        	{
        		var configService:HTTPService = new HTTPService();
        		configService.url = "config.xml";
        		configService.resultFormat = "e4x";
        		configService.addEventListener(ResultEvent.RESULT, configResult);
        		configService.addEventListener(FaultEvent.FAULT, configFault);
        		configService.send();
        	}
 
            private function configResult(event:ResultEvent):void
            {
            	var configXML:XML = event.result as XML;
            	var serviceList:XMLList = configXML..service;
            	for (var i:int = 0; i < serviceList.length(); i++)
            	{
             		var serviceName:String = serviceList[i].name;
            		var serviceURL:String = serviceList[i].url;
            		var serviceAlpha:int = serviceList[i].alpha;
            		var serviceVisible:Boolean = serviceList[i].visible;
            		var serviceLayers:ArrayCollection = new ArrayCollection(serviceList[i].layers.split(","));
 
            		var tLayer:ArcGISDynamicMapServiceLayer = new ArcGISDynamicMapServiceLayer(serviceURL); 
            		tLayer.id = serviceName;
            		tLayer.visible = serviceVisible;
            		tLayer.alpha = serviceAlpha;
            		tLayer.visibleLayers = serviceLayers;
            		myMap.addLayer(tLayer);	
            	}          	
            }
 
            private function configFault(event:FaultEvent):void
            {
            	Alert.show(event.fault.faultString);
            }
 
 
            private function layerShowHandler(event:FlexEvent):void
            {
                // update the LODs/zoomslider to use/show the levels for the selected base map
                var tiledLayer:TiledMapServiceLayer = event.target as TiledMapServiceLayer;
                myMap.lods = tiledLayer.tileInfo.lods;
            }
 
			//add a callback for js to flex communication
			public function init():void
			{
				initXML();
			  if (flash.external.ExternalInterface.available)
			    flash.external.ExternalInterface.addCallback("changeExtent", changeExtent);     
			}
 
			//call from js
			public function changeExtent(sextent:String):void	
			{
				//var avals = sextent.split("|");
				//var ext:Extent = new Extent(new Number(avals[0]),new Number(avals[2]),new Number(avals[1]),new Number(avals[3]));
				//ext.spatialReference = myMap.spatialReference;
				//myMap.extent = ext;
			}
 
            public function onBasemapCreationComplete():void
			{
            	myMap.addEventListener(MouseEvent.CLICK, pushExtentEvent);
            }
 
     		//send extents to html/js
     		public function pushExtentEvent(event:MouseEvent):void           
      	 	{
      	 		if (flash.external.ExternalInterface.available)
			     {
			       const mapPoint:MapPoint = myMap.toMapFromStage(event.stageX, event.stageY);
			       var extent:Array = new Array();
			       flash.external.ExternalInterface.call("displayExtent", mapPoint.y.toFixed(6), mapPoint.x.toFixed(6));
			     }        
      	 	}
      	 	private function onMapClick(event:MapMouseEvent):void
      	 	{    	 		
      	 		myMap.infoWindow.label = "Event Location ";
      	 		myMap.infoWindow.show(event.mapPoint);
      	 	}
        ]]>
    </mx>
    <esri :Map id="myMap" width="100%" height="100%" mapClick="onMapClick(event)" >
      </esri><esri :extent>
        </esri><esri :Extent xmin="-122.2" ymin="24.89" xmax="-70.59" ymax="46.92"  >
          <esri :SpatialReference wkid="4326"/>
        </esri>
 
        <esri :ArcGISTiledMapServiceLayer
            visible="{bb.selectedIndex == 0}"
            show="layerShowHandler(event)"
            url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer" 
            creationComplete="onBasemapCreationComplete()" />
        <esri :ArcGISTiledMapServiceLayer
            visible="{bb.selectedIndex == 1}"
            show="layerShowHandler(event)"
            url="http://server.arcgisonline.com/ArcGIS/rest/services/NGS_Topo_US_2D/MapServer" 
            creationComplete="onBasemapCreationComplete()"/>
        <esri :ArcGISTiledMapServiceLayer
            visible="{bb.selectedIndex == 2}"
            show="layerShowHandler(event)"
            url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer" 
            creationComplete="onBasemapCreationComplete()" />
 
    <mx :ToggleButtonBar id="bb" horizontalGap="2" selectedIndex="0" themeColor="0x00FF00" right="5" top="5">
        </mx><mx :dataProvider>
            </mx><mx :Array>
                </mx><mx :String>Streets</mx>
                <mx :String>Topo</mx>
                <mx :String>Imagery</mx>

First, note the code block that references an external XML file (config.xml). It really doesn’t get any easier than this. What’s really cool about this XML file is that basically, all the web service configuration options from the ArcGIS Service are configured through here. You can traverse through the config.xml file by referencing the node you want in the XMLList or serviceList:XMLList.

var serviceName:String = serviceList[i].name;
var serviceURL:String = serviceList[i].url;
var serviceAlpha:int = serviceList[i].alpha;
var serviceVisible:Boolean = serviceList[i].visible;
var serviceLayers:ArrayCollection = new ArrayCollection(serviceList[i].layers.split(“,”));

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private function initXML():void
        	{
        		var configService:HTTPService = new HTTPService();
        		configService.url = "config.xml";
        		configService.resultFormat = "e4x";
        		configService.addEventListener(ResultEvent.RESULT, configResult);
        		configService.addEventListener(FaultEvent.FAULT, configFault);
        		configService.send();
        	}
 
            private function configResult(event:ResultEvent):void
            {
            	var configXML:XML = event.result as XML;
            	var serviceList:XMLList = configXML..service;
            	for (var i:int = 0; i < serviceList.length(); i++)
            	{
             		var serviceName:String = serviceList[i].name;
            		var serviceURL:String = serviceList[i].url;
            		var serviceAlpha:int = serviceList[i].alpha;
            		var serviceVisible:Boolean = serviceList[i].visible;
            		var serviceLayers:ArrayCollection = new ArrayCollection(serviceList[i].layers.split(","));
 
            		var tLayer:ArcGISDynamicMapServiceLayer = new ArcGISDynamicMapServiceLayer(serviceURL); 
            		tLayer.id = serviceName;
            		tLayer.visible = serviceVisible;
            		tLayer.alpha = serviceAlpha;
            		tLayer.visibleLayers = serviceLayers;
            		myMap.addLayer(tLayer);	
            	}          	
            }
 
            private function configFault(event:FaultEvent):void
            {
            	Alert.show(event.fault.faultString);
            }

The XML is below: Note the configuration options that you would normally have to set in your compiled code. I just included the service name, url, alpha and visibility in this demonstration. There are many more options to choose from…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
< ?xml version="1.0"?>
<configuration>
<service>
	<name>
	Service Name
	</name>
	<url>
	http://yoururl.com/yourserver/rest/services/BaseMap/MapServer/
	</url>
	<alpha>
	1.0
	</alpha>
	<visible>
	true
	</visible>
	<layers>
	0,1
	</layers>
</service>
</configuration>

The next thing to take a look at is the fact that there is an Event that grabs the Latitude and Longitude from your map when it's clicked on (MouseEvent.CLICK). Moreover, it passes them to an HTML Input text box in the host page through an ExternalInterface call. The corresponding javascript should resemble the following. Note the < %= this.latitude.ClientID %> and < %= this.longitude.ClientID %> as they are required if you are embedding this in a ASP.NET for C# application.

?View Code JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script language="JavaScript" type="text/javascript">
//call flex function changeExtent
function mapextentchange(value)
{
	if(value != ''){
		getFlexApp('FlexMaps').changeExtent(value);
	}	
}
 
//called from flex
function displayExtent(latitude,longitude)
{
	document.getElementById("< %= this.latitude.ClientID %>").value = latitude;
	document.getElementById("< %= this.longitude.ClientID %>").value = longitude;
}
</script>

Finally, I snagged an example off of the ESRI Flex Community page that allowed me to toggle between Streetmap, Topographic Maps and Imagery data. As the selected Index changes, the base map changes...

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        <esri :ArcGISTiledMapServiceLayer
            visible="{bb.selectedIndex == 0}"
            show="layerShowHandler(event)"
            url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer" 
            creationComplete="onBasemapCreationComplete()" />
        <esri :ArcGISTiledMapServiceLayer
            visible="{bb.selectedIndex == 1}"
            show="layerShowHandler(event)"
            url="http://server.arcgisonline.com/ArcGIS/rest/services/NGS_Topo_US_2D/MapServer" 
            creationComplete="onBasemapCreationComplete()"/>
        <esri :ArcGISTiledMapServiceLayer
            visible="{bb.selectedIndex == 2}"
            show="layerShowHandler(event)"
            url="http://server.arcgisonline.com/ArcGIS/rest/services/ESRI_Imagery_World_2D/MapServer" 
            creationComplete="onBasemapCreationComplete()" />
 
    <mx :ToggleButtonBar id="bb" horizontalGap="2" selectedIndex="0" themeColor="0x00FF00" right="5" top="5">
        </mx><mx :dataProvider>
            </mx><mx :Array>
                </mx><mx :String>Streets</mx>
                <mx :String>Topo</mx>
                <mx :String>Imagery</mx>

So, I've had fun playing with this an I've actually been able to use the entire script in a project I was a part of. I hope that someone else gets as much use out of it as I did.

SpatiaLite 2.4.0 RC-0

2009 November 25
by Adam Estrada

Alessando announced today that the latest version of SpatiaLite is ready for download. This is a Release Candidate so he is really looking for feedback! Please download it and test if you can…Check out the impressive RoadMap for this Open Source project.

Road Map
libspatialite
  • supporting 3D geometries [XY, XYZ, XYM, XYZM]
  • implementing compressed geometries
  • supporting ST_alias-names for OGC functions
  • supporting SQLite v.3.6.20, and consequently truely enforcing Foreign Key constraints
  • supporting libproj 4.7.0 [lots of new SRIDs added]
  • added SQL functions supporting GreatCircle and Geodesic distance/lenght computation
  • added SQL functions for unit conversions [meter to feet and alike]
  • fixed several SHP related issues
  • supporting 3D shapefiles
spatialite-tools
  • added a new CLI tool spatialite_osm supporting direct acquisition of

    road networks from Open Street Map XML/OSM datasets
librasterlite
  • minor bug-fixing and code cleaning
  • now supporting the MSVC compiler as well
Important notice:
Alexender Simakov [the Wavelet codec developer]

is currently evaluating my proposal to re-release his libepsilon under the LPGL.

accordingly to this librasterlite will be re-released under LGPL as soon as possible
spatialite-gui
  • supporting an interactive Query / View builder tool
  • added the capability to DROP or RENAME columns
spatialite-gis
  • supporting View-based layers [read-only]
  • supporting VirtualSHP-based layers [read-only]
  • supporting map labeling with optional anti-overlap optimization
  • supporting interactive routing
  • supporting map output generation as:
    • PNG, JPEG, TIFF images
    • PDF documents
    • SVG documents
Quantum GIS QGis
  • updated data provider released on QGis trunk SVN

I particularly like the fact that it now supports routing using OpenStreetMap! Way cool, Sando!