Tuesday, 7 February 2012

mg-desktop: advanced features

Previously, I showed you how to get a basic mg-desktop application up and running.

For this post, I am going to introduce some of the more advanced features and functionality of mg-desktop

Viewer Properties


The mg-desktop viewer (MgMapViewer) exposes a whole bunch of properties that can control the behaviour and appearance of certain features in the map viewer.


Each property is explained below

  • ConvertTiledGroupsToNonTiled - Tiled Layer Groups will be treated as regular map groups, allowing for such groups and layers to be shown in the viewer. This is a simple workaround for the viewer's current inability to display map tiles. 
  • SelectionColor - Controls the color of selected features in the viewer
  • ShowVertexCoordinatesWhenDigitizing - When you are digitizing, the vertex coordinates are shown at each node of the currently digitized geometry. For circles and points, no vertex coordinates are displayed because such shapes have no vertices. For example, digitizing a polygon would look like this:

  • ZoomInFactor - The zoom scale multiplier to apply for zooming in
  • ZoomOutFactor - The zoom scale multiplier to apply for zooming out

Digitizing and Measuring

Like the AJAX and Fusion viewers, the mg-desktop viewer has built-in geometry digitizing functionality.

   1:  public interface IMapViewer
   2:  {
   3:      //
   4:      // Summary:
   5:      //     Starts the digitization process for a circle
   6:      //
   7:      // Parameters:
   8:      //   callback:
   9:      //     The callback to be invoked when the digitization process completes
  10:      void DigitizeCircle(CircleDigitizationCallback callback);
  11:      //
  12:      // Summary:
  13:      //     Starts the digitization process for a line
  14:      //
  15:      // Parameters:
  16:      //   callback:
  17:      //     The callback to be invoked when the digitization process completes
  18:      void DigitizeLine(LineDigitizationCallback callback);
  19:      //
  20:      // Summary:
  21:      //     Starts the digitization process for a line string (polyline)
  22:      //
  23:      // Parameters:
  24:      //   callback:
  25:      //     The callback to be invoked when the digitization process completes
  26:      void DigitizeLineString(LineStringDigitizationCallback callback);
  27:      //
  28:      // Summary:
  29:      //     Starts the digitization process for a point
  30:      //
  31:      // Parameters:
  32:      //   callback:
  33:      //     The callback to be invoked when the digitization process completes
  34:      void DigitizePoint(PointDigitizationCallback callback);
  35:      //
  36:      // Summary:
  37:      //     Starts the digitization process for a polygon
  38:      //
  39:      // Parameters:
  40:      //   callback:
  41:      //     The callback to be invoked when the digitization process completes
  42:      void DigitizePolygon(PolygonDigitizationCallback callback);
  43:      //
  44:      // Summary:
  45:      //     Starts the digitization process for a rectangle
  46:      //
  47:      // Parameters:
  48:      //   callback:
  49:      //     The callback to be invoked when the digitization process completes
  50:      void DigitizeRectangle(RectangleDigitizationCallback callback);
  51:  }

The digitization API is very similar to the AJAX viewer. To digitize a circle for example, you would call the API like so:

   1:  mgMapViewer1.DigitizeCircle(OnCircleDigitized);

The OnCircleDigitized method must match the signature of the CircleDigitizationCallback delegate, which looks like this:

   1:  private void OnCircleDigitized(double x, double y, double radius)
   2:  {
   3:      //x, y is the circle center in the map's coordinates
   4:      //radius is the circle's radius
   5:  }

While digitizing, the digitization process can be aborted by pressing the ESC key. If digitization is aborted, the digitization callback will not be called by the viewer.

Tools like measuring can be built on this functional primitive of digitization. Here's an example using a line digitizer:

   1:  private void btnMeasure_Click(object sender, EventArgs e)
   2:  {
   3:      mgMapViewer1.DigitizeLine(OnLineDigitized);
   4:  }
   5:   
   6:  private void OnLineDigitized(double x1, double y1, double x2, double y2)
   7:  {
   8:      MgMapBase map = mgMapViewer1.GetMap();
   9:      //Create a coordiante system from the map's SRS
  10:      MgCoordinateSystemFactory csFactory = new MgCoordinateSystemFactory();
  11:      MgCoordinateSystem mapCs = csFactory.Create(map.GetMapSRS());
  12:   
  13:      //Invoke the appropriate measure method depending on the type
  14:      //of coordinate system
  15:      double dist = 0.0;
  16:      if (mapCs.GetType() == MgCoordinateSystemType.Geographic)
  17:          dist = mapCs.MeasureGreatCircleDistance(x1, y1, x2, y2);
  18:      else
  19:          dist = mapCs.MeasureEuclideanDistance(x1, y1, x2, y2);
  20:   
  21:      //Convert this distance to meters
  22:      dist = mapCs.ConvertCoordinateSystemUnitsToMeters(dist);
  23:   
  24:      MessageBox.Show("Distance is: " + dist + " meters");
  25:  }

Using the digitizing API combined with the existing MgGeometry and MgCoordinateSystem APIs, you can measure in ways other than simple point A - point B distance measuring.

Redlining


When you combine digitization with feature manipulation provided by the MapGuide API, you have the basis for redlining. Here's an example of creating point features from digitized points:

   1:  private MgdLayer _pointLayer;
   2:   
   3:  private void btnDrawPoint_Click(object sender, EventArgs e)
   4:  {
   5:      mgMapViewer1.DigitizePoint(OnPointDrawn);
   6:  }
   7:   
   8:  private void OnPointDrawn(double x, double y)
   9:  {
  10:      if (_pointLayer == null) //Our point layer doesn't exist
  11:          CreateRedlineLayer();
  12:      
  13:      //Now insert our point. This code should look familiar
  14:      //to you, setting up the MgPropertyCollection for insertion
  15:      MgPropertyCollection props = new MgPropertyCollection();
  16:      MgWktReaderWriter wktRw = new MgWktReaderWriter();
  17:      MgAgfReaderWriter agfRw = new MgAgfReaderWriter();
  18:   
  19:      MgGeometry geom = wktRw.Read("POINT (" + x + " " + y + ")");
  20:      MgByteReader agf = agfRw.Write(geom);
  21:   
  22:      MgGeometryProperty geomProp = new MgGeometryProperty("Geometry", agf);
  23:      props.Add(geomProp);
  24:   
  25:      //Here's where we differ from the official MapGuide API
  26:      //instead of a monolithic UpdateFeatures() that tries to 
  27:      //do everything, we have individual InsertFeatures/DeleteFeatures/UpdateFeatures
  28:      //methods. So here's the mg-desktop way
  29:   
  30:      MgFeatureReader result = _pointLayer.InsertFeatures(props);
  31:      result.Close();
  32:   
  33:      //Or if you have have access to the MgdLayer instance
  34:      /*
  35:      MgResourceIdentifier fsId = new MgResourceIdentifier(_pointLayer.GetFeatureSourceId());
  36:      MgServiceFactory factory = new MgServiceFactory();
  37:      MgdFeatureService featSvc = (MgdFeatureService)factory.CreateService(MgServiceType.FeatureService);
  38:      MgFeatureReader fr = featSvc.InsertFeatures(fsId, "Default:Redline", props);
  39:      fr.Close();
  40:       */
  41:      
  42:      //Now refresh to see your newly drawn point
  43:      mgMapViewer1.RefreshMap();
  44:  }
  45:   
  46:  private void CreateRedlineLayer()
  47:  {
  48:      MgMapBase map = mgMapViewer1.GetMap();
  49:      MgServiceFactory fact = new MgServiceFactory();
  50:      MgdFeatureService featSvc = (MgdFeatureService)fact.CreateService(MgServiceType.FeatureService);
  51:      MgResourceService resSvc = (MgResourceService)fact.CreateService(MgServiceType.ResourceService);
  52:   
  53:      //Note that mg-desktop does not have a concept of sessions like the
  54:      //official MapGuide API, but it *does* allow session-based resources 
  55:      //as a way of having temporary resources. Such resources will reside
  56:      //in a special directory for session resources (specified in Platform.ini)
  57:      //
  58:      //You can plug whatever string as the session id, but the resource identifier
  59:      //must satisfy the session id pattern:
  60:      //
  61:      // Session:<session id string>//Path/To/Your.ResourceType
  62:      //
  63:      //These files are removed with MgPlatform.Terminate(), which is called in this
  64:      //application as part of the exiting process.
  65:      string sessionId = Guid.NewGuid().ToString();
  66:      MgResourceIdentifier fsId = new MgResourceIdentifier("Session:" + sessionId + "//Redline.FeatureSource");
  67:      MgResourceIdentifier ldfId = new MgResourceIdentifier("Session:" + sessionId + "//Redline.LayerDefinition");
  68:   
  69:      //Create our point redline schema. It looks like this:
  70:      //
  71:      // Default
  72:      //    Redline
  73:      //        ID (int32, autogenerated)
  74:      //        Geometry (coordinate system same as map
  75:      string featureClass = "Default:Redline";
  76:      string geometry = "Geometry";
  77:   
  78:      MgFeatureSchema schema = new MgFeatureSchema("Default", "Redline schema");
  79:      MgClassDefinition cls = new MgClassDefinition();
  80:      cls.Name = "Redline";
  81:   
  82:      MgDataPropertyDefinition id = new MgDataPropertyDefinition("ID");
  83:      id.DataType = MgPropertyType.Int32;
  84:      id.SetAutoGeneration(true);
  85:   
  86:      MgGeometricPropertyDefinition geom = new MgGeometricPropertyDefinition(geometry);
  87:      geom.SpatialContextAssociation = "Default";
  88:      geom.GeometryTypes = MgFeatureGeometricType.Curve | MgFeatureGeometricType.Point | MgFeatureGeometricType.Solid | MgFeatureGeometricType.Surface;
  89:   
  90:      MgPropertyDefinitionCollection clsProps = cls.GetProperties();
  91:      clsProps.Add(id);
  92:      clsProps.Add(geom);
  93:   
  94:      MgPropertyDefinitionCollection idProps = cls.GetIdentityProperties();
  95:      idProps.Add(id);
  96:   
  97:      cls.DefaultGeometryPropertyName = geometry;
  98:      MgClassDefinitionCollection classes = schema.GetClasses();
  99:      classes.Add(cls);
 100:   
 101:      //Create the feature source with this schema. We use the map's
 102:      //coordinate system for the feature source to ensure features
 103:      //that we create, will line up with the map
 104:      MgCreateSdfParams create = new MgCreateSdfParams("Default", map.GetMapSRS(), schema);
 105:      featSvc.CreateFeatureSource(fsId, create);
 106:   
 107:      //Then create the layer definition. RedlineLayer.xml contains the template
 108:      string xml = string.Format(File.ReadAllText("RedlineLayer.xml"), fsId.ToString(), featureClass, geometry);
 109:      var bytes = Encoding.UTF8.GetBytes(xml);
 110:      MgByteSource source = new MgByteSource(bytes, bytes.Length);
 111:      resSvc.SetResource(ldfId, source.GetReader(), null);
 112:   
 113:      //Now create the runtime layer and add to map
 114:      _pointLayer = new MgdLayer(ldfId, resSvc);
 115:      _pointLayer.LegendLabel = "Redlining";
 116:      _pointLayer.Name = "Redline";
 117:      _pointLayer.Visible = true;
 118:      _pointLayer.Selectable = true;
 119:      _pointLayer.DisplayInLegend = true;
 120:   
 121:      var layers = map.GetLayers();
 122:      layers.Insert(0, _pointLayer);
 123:  }

An updated sample (building on the previous sample application) containing the above snippets is available for download here

9 comments:

yoroshiku said...

Thank you for your nice blog!

In mg-desktop, how can I getCenter of the map?
I need to draw a circle, whose center coordinates is the map's center.

Dan

Jackie Ng said...

call GetMap() on the viewer object

That returns the MgdMap object (that inherits from MgMapBase) that has a ViewCenter property, which is the map's center

yoroshiku said...
This comment has been removed by the author.
yoroshiku said...

Thank you again for your helpful blog. I have another question on how to use my own Mapguide package.

I try to use my map.mgp but no success. The map.mgp is created by Mapguide Maestro 4.0, by adding a shapefile.
In source code:


MgResourceIdentifier mapDefId = new MgResourceIdentifier("Library://Maps/4N.MapDefinition");
....
string filename = "E:\\projects\\4N\\Mapguide\\4N.mgp";

When run the project in debuging mode, it stops at

resSvc.ApplyResourcePackage(reader);


The error is:
Not implemented
Or An exception occurred in DWF component.\nBad zip file

The error in details:

OSGeo.MapGuide.MgNotImplementedException was unhandled
Message="Not implemented."
Source="OSGeo.MapGuide.Desktop"
StackTrace:
- MgLibraryRepositoryManager.ApplyResourcePackage() line 205 file c:\mg-trunk\mgdev\desktop\mgdesktop\Services/ResourceService.cpp
- MgLibraryRepositoryManager.LoadResourcePackage() line 235 file c:\mg-trunk\mgdev\desktop\mgdesktop\Services/ResourceService.cpp
- MgResourcePackageLoader.Start() line 133 file c:\mg-trunk\mgdev\desktop\mgdesktop\Services/Resource/ResourcePackageLoader.cpp
- MgResourcePackageLoader.PerformOperation() line 212 file c:\mg-trunk\mgdev\desktop\mgdesktop\Services/Resource/ResourcePackageLoader.cpp
- MgResourcePackageLoader.UpdateRepository() line 253 file c:\mg-trunk\mgdev\desktop\mgdesktop\Services/Resource/ResourcePackageLoader.cpp
- MgdResourceService::UpdateRepository() line 51 file c:\mg-trunk\mgdev\desktop\mgdesktop\Services/ResourceService.cpp
InnerException:


Please give me an advice.
Thank you alot.
Dan

Jackie Ng said...

Did the package you create in Maestro load properly in MapGuide Server itself?

mg-desktop is using the exact package loading code

yoroshiku said...

Using Mapguide Maestro 4.0 and Mapguide Opensource Server 2.2, I recreated again a package to test.
1) I add a shapefile of polygon, with WGS84 coordinate system.
2) I add a map definition based on the layer automatically created based on the shapefile.
3) I save the package as mgp file.

The layer can be viewed in web browser.

However, the error still appears.

Can you show me how to creat a package like Sheboygan.mgp?

yoroshiku said...

I've just tried your new mg-desktop binary but no luck.

Please guide me to solve the error.

Kent Kuholski said...

Hi,
Do you have a step by step way to create a database with mapguide open source. I am create a database for clients parcels. I work with Map 3d and open source mapguide with Maestro.

Kent Kuholski said...

I am trying to step my open source mapguide with maestro. I work for a small civil engineering firm. I like to have a database of all our clients parcels. The problem is I want the whole company to see the database. They don't have the software I have.