Today I expand on yesterday's post about MapFish Print and Leaflet, to include vector layers. MFP's documentation doesn't even mention that this exists; there is a brief mention in a MFP commit note, but zero documentation. Fortunately, the MapFish Print demo app uses OpenLayers, and I have Firebug, so analyzing the output was possible. Took me all day to get it right, but possible.
To add a vector feature, you expand on the "layers" parameter in the client spec. You saw yesterday that I constructed "layers" as a list, adding items one by one depending on whether the layer is enabled. Same idea here, except that instead of sending a WMS layer spec we're sending a spec for a Vector layer and then for a GeoJSON feature within that Vector layer.
The demo generates output like this:
I'm going to go slightly differently, though, and have each vector feature in its own Layer,since we're not sure that each feature exists. Trust me, it'll be fine.{"type":"Vector", "styles":{ "1":{"externalGraphic":"http://openlayers.org/dev/img/marker-blue.png","strokeColor":"red","fillColor":"red","fillOpacity":0.7,"strokeWidth":2,"pointRadius":12} }, "styleProperty":"_gx_style", "geoJson":{"type":"FeatureCollection", "features":[ {"type":"Feature","id":"OpenLayers.Feature.Vector_52","properties":{"_gx_style":1},"geometry":{"type":"Polygon","coordinates":[[[15,47],[16,48],[14,49],[15,47]]]}}, {"type":"Feature","id":"OpenLayers.Feature.Vector_61","properties":{"_gx_style":1},"geometry":{"type":"LineString","coordinates":[[15,48],[16,47],[17,46]]}}, {"type":"Feature","id":"OpenLayers.Feature.Vector_64","properties":{"_gx_style":1},"geometry":{"type":"Point","coordinates":[16,46]}}]} ], "name":"vector","opacity":1} }
POINT MARKER
In our case, it's a single marker that may exist. For your map, you may have multiple markers so you may need to write this as a loop rather than a single "if" But still, this is the real meat of how to get a marker into your map.
// MARKER_TARGET is a L.Marker, ICON_TARGET is a L.Icon subclass used by the marker // wgsToGoogle() was posted yesterday; it converts a LatLng in WGS84 to a [x,y] in Web Mercator if (MARKER_TARGET && MAP.hasLayer(MARKER_TARGET) ) { var iconurl = ICON_TARGET.prototype.iconUrl; var projdot = wgsToGoogle(MARKER_TARGET.getLatLng()); var iconxoff = -10; // offset to place the marker; MFP drifts it for some reason var iconyoff = 0; // offset to place the marker; MFP drifts it for some reason var iconsize = 15; // the scaling factor for the icon; determined to taste through trial and error // all of this is required: styleProperty and properties form the link to a style index, fillOpacity really works layers[layers.length] = { type:"Vector", name:"Target Marker", opacity:1.0, styleProperty: "style_index", styles:{ "default":{ externalGraphic:iconurl, fillOpacity:1.0, pointRadius:iconsize, graphicXOffset: iconxoff, graphicYOffset: iconyoff } }, geoJson:{ type:"FeatureCollection", features:[ { type:"Feature", properties:{ style_index:"default" }, geometry:{ type:"Point", coordinates:projdot } } ] } }; }
Some of this stuff is familiar to OpenLayers folks.
You'll see that the "styles" supplies a single style ("default"), that this is referenced by the "properties.style_index", and that "style_index" is made special by it being the styleProperty. Note that the iconurl must be a full path including http://server.com/ since MFP won't know the relative path to the icon.
The Point geometry is defined within the Feature; the format is a single coordinate pair [x,y] and recall that we're using Web Mercator. As such, the returned array from wgsToGoogle() is perfect for this situation.
LINESTRING AND MULTILINESTRING
// DIRECTIONS_LINE is either a L.Polyline or a L.MultiPolyline, so we support both // DIRECTIONS_LINE_STYLE is an {object} of L.Path options, e.g. color and opacity // we use wgsToGoogle() on each vertex so the resulting geometry is in the right SRS if (DIRECTIONS_LINE && MAP.hasLayer(DIRECTIONS_LINE) ) { // the directions line, using either a single Polyline (not Google driving directions) or a MultiPolyline (Google) // Construct a list-of-lists multilinestring. Remember that OpenLayers and MFP do lng,lat instead of lat,lng var vertices = []; if (DIRECTIONS_LINE.getLatLngs) { // a single Polyline // collect the coordinates into a list, then make that list the only list within "vertices" (a multilinestring with 1 linestring component) var vx = DIRECTIONS_LINE.getLatLngs(); for (var i=0, l=vx.length; i<l; i++) { vertices[vertices.length] = wgsToGoogle([ vx[i].lng, vx[i].lat ]); } vertices = [ vertices ]; } else { // a MultiPolyline // use non-API methods to iterate over the line components, collecting them into "vertices" to form a list of lists for (var li in DIRECTIONS_LINE._layers) { var subline = DIRECTIONS_LINE._layers[li]; var subverts = []; for (var i=0, l=subline._latlngs.length; i<l; i++) { subverts[subverts.length] = wgsToGoogle([ subline._latlngs[i].lng, subline._latlngs[i].lat ]); } vertices[vertices.length] = subverts; } } // the styling is simply pulled from the styling constant var opacity = DIRECTIONS_LINE_STYLE.opacity; var color = DIRECTIONS_LINE_STYLE.color; var weight = 3; layers[layers.length] = { type:"Vector", name:"Directions Line", opacity:opacity, styles:{ "default":{ strokeColor:color, strokeWidth:weight, strokeLinecap:"round" } }, styleProperty:"style_index", geoJson:{ type: "FeatureCollection", features:[ { type:"Feature", properties:{"style_index":"default"}, geometry:{ type:"MultiLineString", coordinates:vertices } } ] } }; }
Not a lot new to say about this one that wasn't said about the point. Again, similar to OpenLayers styles. The vertices are in [x,y] per OGC spec, and are in Web Mercator.
POLYGONS
This project doesn't have any vector polygons to print, so it didn't come up.
CONCLUSION
All told, this finally got printing working! We have base maps, overlays, markers, and lines all drawing into the PDF, and the map isn't unduly distorted. Success!
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.