UNPKG

mmg

Version:

Simple markers for Modest Maps

307 lines (263 loc) 9.38 kB
function mmg() { [].indexOf||(Array.prototype.indexOf=function(a,b,c){for(c=this.length,b=(c+~~b)%c;b<c&&(!(b in this)||this[b]!==a);b++);return b^c?b:-1;}); var m = {}, // external list of geojson features features = [], // internal list of markers markers = [], // the absolute position of the parent element position = null, // a factory function for creating DOM elements out of // GeoJSON objects factory = null, // a sorter function for sorting GeoJSON objects // in the DOM sorter = null, // a list of urls from which features can be loaded. // these can be templated with {z}, {x}, and {y} urls, // map bounds left = null, right = null; // reposition a single marker element function reposition(marker) { // remember the tile coordinate so we don't have to reproject every time if (!marker.coord) marker.coord = m.map.locationCoordinate(marker.location); var pos = m.map.coordinatePoint(marker.coord); var pos_loc; // If this point has wound around the world, adjust its position // to the new, onscreen location if (pos.x < 0) { pos_loc = new MM.Location(marker.location.lat, marker.location.lon); pos_loc.lon += Math.ceil((left.lon - marker.location.lon) / 360) * 360; pos = m.map.locationPoint(pos_loc); marker.coord = m.map.locationCoordinate(pos_loc); } else if (pos.x > m.map.dimensions.x) { pos_loc = new MM.Location(marker.location.lat, marker.location.lon); pos_loc.lon -= Math.ceil((marker.location.lon - right.lon) / 360) * 360; pos = m.map.locationPoint(pos_loc); marker.coord = m.map.locationCoordinate(pos_loc); } pos.scale = 1; pos.width = pos.height = 0; MM.moveElement(marker.element, pos); } m.draw = function() { if (!m.map) return; left = m.map.pointLocation(new MM.Point(0, 0)); right = m.map.pointLocation(new MM.Point(m.map.dimensions.x, 0)); for (var i = 0; i < markers.length; i++) { reposition(markers[i]); } }; m.add = function(marker) { if (!marker || !marker.element) return null; parent.appendChild(marker.element); markers.push(marker); return marker; }; m.remove = function(marker) { if (!marker) return null; parent.removeChild(marker.element); markers.splice(markers.indexOf(marker), 1); return marker; }; m.markers = function(x) { if (!arguments.length) return markers; }; // Public data interface m.features = function(x) { // Return features if (!arguments.length) return features; // Clear features while (parent.hasChildNodes()) { // removing lastChild iteratively is faster than // innerHTML = '' // http://jsperf.com/innerhtml-vs-removechild-yo/2 parent.removeChild(parent.lastChild); } // clear markers representation markers = []; // Set features if (!x) x = []; features = x; features.sort(sorter); for (var i = 0; i < x.length; i++) { m.add({ element: factory(x[i]), location: new MM.Location(x[i].geometry.coordinates[1], x[i].geometry.coordinates[0]), data: x[i] }); } if (m.map && m.map.coordinate) m.map.draw(); return m; }; m.url = function(x, callback) { if (!arguments.length) return urls; if (typeof reqwest === 'undefined') throw 'reqwest is required for url loading'; if (typeof x === 'string') x = [x]; urls = x; function add_features(x) { if (x && x.features) m.features(x.features); if (callback) callback(x.features, m); } reqwest((urls[0].match(/geojsonp$/)) ? { url: urls[0] + (~urls[0].indexOf('?') ? '&' : '?') + 'callback=grid', type: 'jsonp', jsonpCallback: 'callback', success: add_features, error: add_features } : { url: urls[0], type: 'json', success: add_features, error: add_features }); return m; }; m.extent = function() { var ext = [{ lat: Infinity, lon: Infinity}, { lat: -Infinity, lon: -Infinity }]; var ft = m.features(); for (var i = 0; i < ft.length; i++) { var coords = ft[i].geometry.coordinates; if (coords[0] < ext[0].lon) ext[0].lon = coords[0]; if (coords[1] < ext[0].lat) ext[0].lat = coords[1]; if (coords[0] > ext[1].lon) ext[1].lon = coords[0]; if (coords[1] > ext[1].lat) ext[1].lat = coords[1]; } return ext; }; // Factory interface m.factory = function(x) { if (!arguments.length) return factory; factory = x; return m; }; m.factory(function defaultFactory(feature) { var d = document.createElement('div'); d.className = 'mmg-default'; d.style.position = 'absolute'; return d; }); m.sort = function(x) { if (!arguments.length) return sorter; sorter = x; return m; }; m.sort(function(a, b) { return b.geometry.coordinates[1] - a.geometry.coordinates[1]; }); m.destroy = function() { if (this.parent.parentNode) { this.parent.parentNode.removeChild(this.parent); } }; // The parent DOM element var parent = document.createElement('div'); parent.style.cssText = 'position: absolute; top: 0px;' + 'left: 0px; width: 100%; height: 100%; margin: 0; padding: 0; z-index: 0'; m.parent = parent; return m; } function mmg_interaction(mmg) { var mi = {}, tooltips = [], exclusive = true, hide_on_move = true, formatter; mi.formatter = function(x) { if (!arguments.length) return formatter; formatter = x; return mi; }; mi.formatter(function(feature) { var o = '', props = feature.properties; if (props.title) { o += '<strong>' + props.title + '</strong><br />'; } if (props.description) { o += props.description; } return o; }); mi.hide_on_move = function(x) { if (!arguments.length) return hide_on_move; hide_on_move = x; return mi; }; mi.exclusive = function(x) { if (!arguments.length) return exclusive; exclusive = x; return mi; }; mi.bind_marker = function(marker) { marker.element.onclick = function(e) { if (exclusive && tooltips.length > 0) { mmg.remove(tooltips.pop()); } var tooltip = document.createElement('div'); tooltip.className = 'wax-movetip'; var intip = tooltip.appendChild(document.createElement('div')); intip.className = 'wax-intip'; intip.innerHTML = formatter(marker.data); // Here we're adding the tooltip to the dom briefly // to gauge its size. There should be a better way to do this. document.body.appendChild(tooltip); intip.style.marginTop = -( (marker.element.offsetHeight * 0.5) + tooltip.offsetHeight + 10) + 'px'; document.body.removeChild(tooltip); var t = { element: tooltip, data: {}, location: marker.location.copy() }; tooltips.push(t); mmg.add(t); mmg.draw(); }; }; if (mmg && mmg.map) { mmg.map.addCallback('panned', function() { if (hide_on_move) { while (tooltips.length) { mmg.remove(tooltips.pop()); } } }); var markers = mmg.markers(); for (var i = 0; i < markers.length; i++) { mi.bind_marker(markers[i]); } } else { if (console) console.log('mmg must be added to a map before interaction is assigned'); } return mi; } function simplestyle_factory(feature) { var sizes = { small: [20, 50], medium: [30, 70], large: [35, 90] }; var fp = feature.properties || {}; var size = fp['marker-size'] || 'medium'; var symbol = (fp['marker-symbol']) ? '-' + fp['marker-symbol'] : ''; var color = fp['marker-color'] || '7e7e7e'; color = color.replace('#', ''); var d = document.createElement('div'); d.className = 'simplestyle-marker'; var ds = d.style; ds.position = 'absolute'; ds.width = sizes[size][0] + 'px'; ds.height = sizes[size][1] + 'px'; ds.marginTop = -(sizes[size][1] / 2) + 'px'; ds.marginLeft = -(sizes[size][0] / 2) + 'px'; ds.backgroundImage = 'url(http://a.tiles.mapbox.com/v3/marker/' + 'pin-' + size[0] + symbol + '+' + color + '.png)'; ds.textIndent = '-10000px'; d.innerHTML = fp.title || ''; return d; }