Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Added Leaflet support and an example showing basic features #101

Merged
merged 19 commits into from

8 participants

@palewire

This is in response to the discussion in issue #97. In the patch you can find a simple Leaflet plug-in that provides support for tile layers, popups, custom icons, polylines and other basic functions. As discussed in the issue, it remains undecided what to do for the default tile layer. I'd like to notify @pmascari, @freyfogle and @nrabinowitz about this pull request.

@palewire

After talking with @freyfogle, I've patched some fixes that he recommends which you can see in the commit directly above this message. I don't know the timeline for @mapstraction updates but, at the risk of being annoying, I'll drop some github mentions for @dezfowler or @ajturner incase you all have anything else they'd like to see happen with this pull request before it's considered for integration.

@ajturner
Owner

Thanks Ben - playing with this now.

@palewire

Excellent! Let me know if I can help.

@ajturner
Owner

I made a version of test/core.htm for testing Leaflet and it didn't work. For one, Leaflet requires a center and zoom to start and also a Layer. Then there are a few methods that aren't implemented (e.g. getMapType())

My opinion is that a user should be able to switch to Leaflet with no code modifications. This would mean having to have defaults if none were specified on startup (layer, zoom). I realize this may be then odd for developers that want to override it but it's the reason they're using Mapstraction as a complete wrapper and not just Mapstraction as a set of method name changes.

How do you think we can handle the defaults so that it's just changing 'googlev3' to 'leaflet' in the test file (and including the JS)

@palewire

Hmm. Off the top of my head, I think it would not only require picking a tile provider as the default, but also picking layers to substitute for each map type in googlev3. Looks like it offers four layers: roads, satellite, hybrid and terrain.

Leaflet is a product of CloudMade, so using their tiles might be a natural choice, but their system requires an API key, which we probably shouldn't bake into the library. So that's out, IMHO. Looking at the OpenLayers mapstraction code, I only see one OpenStreetMap layer, so that doesn't point the way. MapQuest seems to offer two OSM based layers, roads and aerial, which would get you about half of what Google offers, but at least gives you something distinct to flop between and could substitute in for mxn.Mapstraction.ROAD and mxn.Mapstraction.SATELLITE.

As far as a center and zoom default, I would pitch just using 0,0 off the coast of Africa since it's an easy-to-understand default that is used by other libraries like OpenLayers.

As far as overriding the default layers, I'm not sure what the best route is. I haven't seen it anywhere else in the library, so I'm not sure if there's a style already developed. But maybe it could involve passing an option to the constructor that tells it to boot without a layer. Though if you substitute a layerset that doesn't correspond to the getMapType code (i.e. say, you provide only one layer when it's crafted to switch between two), it could crash when you run that. So you would probably need getMapType to somehow dynamically generate its options based on the currently loaded layers.

What do you think? Also, I'll tag @freyfogle since I know he has opinions about this stuff.

@palewire

Any thoughts? You want me to go ahead and try these fixes?

@ajturner
Owner

I was hoping someone else would chime in.

As you pointed out - OpenLayers defaults to OSM so it "just works" by flipping the provider and not requiring any keys. Setting 0,0 as the default is fine.

The one issue with this path is that if someone wants to make a good experience by loading immediately into a specific area, the OSM basemap and Zoomed area will possibly appear first before the new setCenter and Zoom and basemap are set. These could be provided as optional settings in the constructor to specify a Location and Zoom level, as well as you point out a basemap.

so for example:

var map = new Mapstraction("map_id", "leaflet",  {
   center: [-122,54], 
   zoom: 8, 
   tilelayer: "http://{s}.tile.cloudmade.com/YOUR-API-KEY/997/256/{z}/{x}/{y}.png"
})

or even make

tilelayer: {
   url:"http://tile.openstreetmap.org/{Z}/{X}/{Y}.png", 
   opacity: 1.0, 
   copyright: "OSM", 
   min_zoom:1, 
   max_zoom: 19, 
   selectable: true
}

we could move the debug variable as an key into that object literal

@freyfogle
Owner

Hi guys,

sorry for my silence. Have simply been swamped with other things.

three comments,

  1. don't let perfect be the enemy of good

  2. I would use mapquest for the example. No key needed, is OSM data, and will hold up if people are lazy and just cut and paste and their app gets ton of use.

  3. for initial coordinates for examples I advocate 51.513286, -0.136626. I think 0,0 is not great since it might lead people to think there's an error since they don't see anything (ie just ocean).

sorry I'm not of more help, am just hammered right now.

@dezfowler
Owner
@palewire

Okie dokie. I took a stab here at using MapQuest as a default tile set, and then integrating both its ROAD and SAT layers to work with setMapType and getMapType. Take a look @ajturner and @freyfogle and let me know if you think we're going in the right direction.

@palewire

FYI, we've got some new patches provided by @samiljan.

@freyfogle freyfogle merged commit 2831f92 into from
@freyfogle
Owner
@pyxis777

I was getting the error "iconUrl not set in options" and found the fix here: Leaflet/Leaflet#826

Mapstraction version 2.0.18 has not updated its leaflet module to include the iconUrl updates so I had to change lines 281-285 of mxn.leaflet.core.js (toProprietary function) because leaflet changed the Marker definition.

OLD:

if (me.iconUrl) {
thisIcon = thisIcon.extend({
iconUrl: me.iconUrl
});
}

NEW:

if (me.iconUrl){
thisIcon = thisIcon.extend({
options: {
iconUrl: me.iconUrl
}
});

@vicchi
Owner

Thanks for the heads-up. Mapstraction v2.0.18 is no longer being actively maintained; all efforts are focused on the forthcoming v3.0.0 release. This bug in Leaflet has already been fixed on the release-3.0 branch as a result of Pull Request #213 in March of this year. See this commit for the details - palewire@85062f0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 21, 2011
  1. @palewire
Commits on Nov 28, 2011
  1. @palewire
  2. @palewire

    Added a check in the init that verifies Leaflet's JS library is prese…

    palewire authored
    …nt, using a style similar to other mxn plug-ins.
  3. @palewire
  4. @palewire

    Removed dangling comma in JS

    palewire authored
Commits on Dec 19, 2011
  1. @palewire
Commits on Dec 24, 2011
  1. @palewire

    Another patch from @freyfogle

    palewire authored
Commits on Feb 3, 2012
  1. @ginkel
Commits on Feb 4, 2012
  1. @ginkel
  2. @palewire

    Merge pull request #1 from tgbyte/master

    palewire authored
    Leaflet click event support and getZoomLevelForBoundingBox
Commits on Feb 10, 2012
  1. @ginkel
  2. @ginkel
  3. @palewire

    Merge pull request #2 from tgbyte/infobubble-events

    palewire authored
    Leaflet info bubble / popup events
  4. @palewire
  5. @palewire

    Added MapQuest as a default tile providers and filled in setMapType s…

    palewire authored
    …o that users can set both Roads and Sat layers using standard Mapstraction methods. Aiming to fulfill the requests for @ajturner and @freyfogle in pull request #101.
  6. @palewire

    Tried to get getMapType to work in a way that @ajturner would like to…

    palewire authored
    … see for pull request #101
Commits on Feb 20, 2012
  1. @samiljan
  2. @samiljan

    Added some polyline options

    samiljan authored
  3. @palewire

    Merge pull request #3 from samiljan/master

    palewire authored
    enableScrollWheelZoom fix and added polyline defaults
This page is out of date. Refresh to see the latest.
Showing with 439 additions and 0 deletions.
  1. +55 −0 examples/leaflet.html
  2. +384 −0 source/mxn.leaflet.core.js
View
55 examples/leaflet.html
@@ -0,0 +1,55 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Mapstraction Examples - leaflet</title>
+<link href="http://leaflet.cloudmade.com/dist/leaflet.css" media="all" rel="stylesheet" type="text/css" />
+<!--[if lte IE 8]><link rel="stylesheet" href="http://leaflet.cloudmade.com/dist/leaflet.ie.css" /><![endif]-->
+<script src="http://leaflet.cloudmade.com/dist/leaflet.js" type="text/javascript"></script>
+<script src="../source/mxn.js?(leaflet)" type="text/javascript"></script>
+<style type="text/css">
+#mapdiv {
+ height: 400px;
+}
+</style>
+<script type="text/javascript">
+//<![CDATA[
+ function initialize() {
+
+ // create mxn object
+ map = new mxn.Mapstraction('mapdiv', 'leaflet');
+ map.setMapType(mxn.Mapstraction.SATELLITE);
+ latlon = new mxn.LatLonPoint(41.84756115424155,-91.70015573501587)
+ latlon2 = new mxn.LatLonPoint(41.88756115424155,-91.67015573501587)
+
+ // put map on page
+ map.setCenterAndZoom(latlon, 12);
+
+ // add a marker
+ marker = new mxn.Marker(latlon);
+ map.addMarker(marker);
+ marker.addData({'infoBubble': 'This is a popup'})
+ marker.openBubble();
+
+ // add a polyline
+ var pl = new mxn.Polyline([
+ latlon,
+ latlon2
+ ]);
+ pl.color = '#00DD55';
+ map.addPolyline(pl);
+
+ map.addSmallControls();
+ }
+//]]>
+</script>
+</head>
+<body onload="initialize();">
+<center>
+<table border='1' width='50%'>
+<tr><td><div id="mapdiv"></div></td>
+</tr>
+</table>
+</center>
+</body>
+</html>
+
+
View
384 source/mxn.leaflet.core.js
@@ -0,0 +1,384 @@
+mxn.register('leaflet', {
+
+Mapstraction: {
+
+ init: function(element, api) {
+ if (typeof(L) != 'undefined') {
+ var me = this;
+ var map = new L.Map(element.id, {
+ zoomControl: false
+ });
+ map.addEventListener('moveend', function(){
+ me.endPan.fire();
+ });
+ map.on("click", function(e) {
+ me.click.fire({'location': new mxn.LatLonPoint(e.latlng.lat, e.latlng.lng)});
+ });
+ map.on("popupopen", function(e) {
+ if (e.popup._source.mxnMarker) {
+ e.popup._source.mxnMarker.openInfoBubble.fire({'bubbleContainer': e.popup._container});
+ }
+ });
+ map.on("popupclose", function(e) {
+ if (e.popup._source.mxnMarker) {
+ e.popup._source.mxnMarker.closeInfoBubble.fire({'bubbleContainer': e.popup._container});
+ }
+ });
+ this.layers = {};
+ this.features = [];
+ this.maps[api] = map;
+ this.setMapType();
+ this.currentMapType = mxn.Mapstraction.ROAD;
+ this.loaded[api] = true;
+ } else {
+ alert(api + ' map script not imported');
+ }
+ },
+
+ applyOptions: function(){
+ if (this.options.enableScrollWheelZoom) {
+ this.maps[this.api].scrollWheelZoom.enable();
+ } else {
+ this.maps[this.api].scrollWheelZoom.disable();
+ }
+ return;
+ },
+
+ resizeTo: function(width, height){
+ this.currentElement.style.width = width;
+ this.currentElement.style.height = height;
+ },
+
+ addControls: function(args) {
+ var map = this.maps[this.api];
+ if (args.zoom) {
+ var zoom = new L.Control.Zoom();
+ map.addControl(zoom);
+ }
+ if (args.map_type) {
+ var layersControl = new L.Control.Layers(this.layers, this.features);
+ map.addControl(layersControl);
+ }
+ },
+
+ addSmallControls: function() {
+ this.addControls({zoom: true, map_type: true});
+ },
+
+ addLargeControls: function() {
+ throw 'Not implemented';
+ },
+
+ addMapTypeControls: function() {
+ throw 'Not implemented';
+ },
+
+ setCenterAndZoom: function(point, zoom) {
+ var map = this.maps[this.api];
+ var pt = point.toProprietary(this.api);
+ map.setView(pt, zoom);
+ },
+
+ addMarker: function(marker, old) {
+ var map = this.maps[this.api];
+ var pin = marker.toProprietary(this.api);
+ map.addLayer(pin);
+ this.features.push(pin);
+ return pin;
+ },
+
+ removeMarker: function(marker) {
+ var map = this.maps[this.api];
+ map.removeLayer(marker.proprietary_marker);
+ },
+
+ declutterMarkers: function(opts) {
+ throw 'Not implemented';
+ },
+
+ addPolyline: function(polyline, old) {
+ var map = this.maps[this.api];
+ polyline = polyline.toProprietary(this.api);
+ map.addLayer(polyline);
+ this.features.push(polyline);
+ return polyline;
+ },
+
+ removePolyline: function(polyline) {
+ var map = this.maps[this.api];
+ map.removeLayer(polyline.proprietary_polyline);
+ },
+
+ getCenter: function() {
+ var map = this.maps[this.api];
+ var pt = map.getCenter();
+ return new mxn.LatLonPoint(pt.lat, pt.lng);
+ },
+
+ setCenter: function(point, options) {
+ var map = this.maps[this.api];
+ var pt = point.toProprietary(this.api);
+ if(options && options.pan) {
+ map.panTo(pt);
+ }
+ else {
+ map.setView(pt, map.getZoom(), true);
+ }
+ },
+
+ setZoom: function(zoom) {
+ var map = this.maps[this.api];
+ map.setZoom(zoom);
+ },
+
+ getZoom: function() {
+ var map = this.maps[this.api];
+ return map.getZoom();
+ },
+
+ getZoomLevelForBoundingBox: function(bbox) {
+ var map = this.maps[this.api];
+ var bounds = new L.LatLngBounds(
+ bbox.getSouthWest().toProprietary(this.api),
+ bbox.getNorthEast().toProprietary(this.api));
+ return map.getBoundsZoom(bounds);
+ },
+
+ setMapType: function(type) {
+ switch(type) {
+ case mxn.Mapstraction.ROAD:
+ this.addTileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png', {
+ name: "Roads",
+ attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">',
+ subdomains: [1,2,3,4]
+ });
+ this.currentMapType = mxn.Mapstraction.ROAD;
+ break;
+ case mxn.Mapstraction.SATELLITE:
+ this.addTileLayer('http://oatile{s}.mqcdn.com/naip/{z}/{x}/{y}.jpg', {
+ name: "Satellite",
+ attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">',
+ subdomains: [1,2,3,4]
+ });
+ this.currentMapType = mxn.Mapstraction.SATELLITE;
+ break;
+ case mxn.Mapstraction.HYBRID:
+ throw 'Not implemented';
+ default:
+ this.addTileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png', {
+ name: "Roads",
+ attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">',
+ subdomains: [1,2,3,4]
+ });
+ this.currentMapType = mxn.Mapstraction.ROAD;
+ }
+ },
+
+ getMapType: function() {
+ return this.currentMapType;
+ },
+
+ getBounds: function () {
+ var map = this.maps[this.api];
+ var box = map.getBounds();
+ var sw = box.getSouthWest();
+ var ne = box.getNorthEast();
+ return new mxn.BoundingBox(sw.lat, sw.lng, ne.lat, ne.lng);
+ },
+
+ setBounds: function(bounds){
+ var map = this.maps[this.api];
+ var sw = bounds.getSouthWest().toProprietary(this.api);
+ var ne = bounds.getNorthEast().toProprietary(this.api);
+ var newBounds = new L.LatLngBounds(sw, ne);
+ map.fitBounds(newBounds);
+ },
+
+ addImageOverlay: function(id, src, opacity, west, south, east, north) {
+ throw 'Not implemented';
+ },
+
+ setImagePosition: function(id, oContext) {
+ throw 'Not implemented';
+ },
+
+ addOverlay: function(url, autoCenterAndZoom) {
+ throw 'Not implemented';
+ },
+
+ addTileLayer: function(tile_url, options) {
+ var layerName;
+ if (options && options.name) {
+ layerName = options.name;
+ delete options.name;
+ } else {
+ layerName = 'Tiles';
+ }
+ this.layers[layerName] = new L.TileLayer(tile_url, options || {});
+ var map = this.maps[this.api];
+ map.addLayer(this.layers[layerName]);
+ },
+
+ toggleTileLayer: function(tile_url) {
+ throw 'Not implemented';
+ },
+
+ getPixelRatio: function() {
+ throw 'Not implemented';
+ },
+
+ mousePosition: function(element) {
+ throw 'Not implemented';
+ },
+
+ openBubble: function(point, content) {
+ var map = this.maps[this.api];
+ var newPoint = point.toProprietary(this.api);
+ var marker = new L.Marker(newPoint);
+ marker.bindPopup(content);
+ map.addLayer(marker);
+ marker.openPopup();
+ },
+
+ closeBubble: function() {
+ var map = this.maps[this.api];
+ map.closePopup();
+ }
+},
+
+LatLonPoint: {
+
+ toProprietary: function() {
+ return new L.LatLng(this.lat,this.lon);
+ },
+
+ fromProprietary: function(point) {
+ this.lat = point.lat();
+ this.lon = point.lng();
+ }
+
+},
+
+Marker: {
+
+ toProprietary: function() {
+ var me = this;
+ var thisIcon = L.Icon;
+ if (me.iconUrl) {
+ thisIcon = thisIcon.extend({
+ iconUrl: me.iconUrl
+ });
+ }
+ if (me.iconSize) {
+ thisIcon = thisIcon.extend({
+ iconSize: new L.Point(me.iconSize[0], me.iconSize[1])
+ });
+ }
+ if (me.iconAnchor) {
+ thisIcon = thisIcon.extend({
+ iconAnchor: new L.Point(me.iconAnchor[0], me.iconAnchor[1])
+ });
+ }
+ if (me.iconShadowUrl) {
+ thisIcon = thisIcon.extend({
+ shadowUrl: me.iconShadowUrl
+ });
+ }
+ if (me.iconShadowSize) {
+ thisIcon = thisIcon.extend({
+ shadowSize: new L.Point(me.iconShadowSize[0], me.iconShadowSize[1])
+ });
+ }
+ var iconObj = new thisIcon();
+ var marker = new L.Marker(
+ this.location.toProprietary('leaflet'),
+ { icon: iconObj }
+ );
+ (function(me, marker) {
+ marker.on("click", function (e) {
+ me.click.fire();
+ });
+ })(me, marker);
+ return marker;
+ },
+
+ openBubble: function() {
+ var pin = this.proprietary_marker;
+ if (this.infoBubble) {
+ pin.mxnMarker = this;
+ pin.bindPopup(this.infoBubble);
+ pin.openPopup();
+ }
+ },
+
+ closeBubble: function() {
+ var pin = this.proprietary_marker;
+ pin.closePopup();
+ },
+
+ hide: function() {
+ var map = this.mapstraction.maps[this.api];
+ map.removeLayer(this.proprietary_marker);
+ },
+
+ show: function() {
+ var map = this.mapstraction.maps[this.api];
+ map.addLayer(this.proprietary_marker);
+ },
+
+ isHidden: function() {
+ var map = this.mapstraction.maps[this.api];
+ if (map.hasLayer(this.proprietary_marker)) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ update: function() {
+ throw 'Not implemented';
+ }
+
+},
+
+Polyline: {
+
+ toProprietary: function() {
+ var points = [];
+ for (var i = 0, length = this.points.length ; i< length; i++){
+ points.push(this.points[i].toProprietary('leaflet'));
+ }
+
+ var polyOptions = {
+ color: this.color || '#000000',
+ opacity: this.opacity || 1.0,
+ weight: this.width || 3,
+ fillColor: this.fillColor || '#000000'
+ };
+
+ if (this.closed) {
+ return new L.Polygon(points, polyOptions);
+ } else {
+ return new L.Polyline(points, polyOptions);
+ }
+ },
+
+ show: function() {
+ this.map.addLayer(this.proprietary_polyline);
+ },
+
+ hide: function() {
+ this.map.removeLayer(this.proprietary_polyline);
+ },
+
+ isHidden: function() {
+ if (this.map.hasLayer(this.proprietary_polyline)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+}
+
+});
+
Something went wrong with that request. Please try again.