1 if (!org) var org = {};
2 if (!org.polymaps) org.polymaps = {};
5 po.version = "2.5.0"; // semver.org
7 var zero = {x: 0, y: 0};
9 svg: "http://www.w3.org/2000/svg",
10 xlink: "http://www.w3.org/1999/xlink"
14 var i = name.indexOf(":");
15 return i < 0 ? name : {
16 space: po.ns[name.substring(0, i)],
17 local: name.substring(i + 1)
26 po.svg = function(type) {
27 return document.createElementNS(po.ns.svg, type);
29 po.transform = function(a, b, c, d, e, f) {
35 if (!arguments.length) {
40 transform.zoomFraction = function(x) {
41 if (!arguments.length) return zoomFraction;
43 zoomDelta = Math.floor(zoomFraction + Math.log(Math.sqrt(a * a + b * b + c * c + d * d)) / Math.LN2);
44 k = Math.pow(2, -zoomDelta);
48 transform.apply = function(x) {
49 var k0 = Math.pow(2, -x.zoom),
50 k1 = Math.pow(2, x.zoom - zoomDelta);
52 column: (a * x.column * k0 + c * x.row * k0 + e) * k1,
53 row: (b * x.column * k0 + d * x.row * k0 + f) * k1,
54 zoom: x.zoom - zoomDelta
58 transform.unapply = function(x) {
59 var k0 = Math.pow(2, -x.zoom),
60 k1 = Math.pow(2, x.zoom + zoomDelta);
62 column: (x.column * k0 * d - x.row * k0 * c - e * d + f * c) / (a * d - b * c) * k1,
63 row: (x.column * k0 * b - x.row * k0 * a - e * b + f * a) / (c * b - d * a) * k1,
64 zoom: x.zoom + zoomDelta
68 transform.toString = function() {
69 return "matrix(" + [a * k, b * k, c * k, d * k].join(" ") + " 0 0)";
72 return transform.zoomFraction(0);
74 po.cache = function(load, unload) {
83 function remove(tile) {
85 if (unload) unload(tile);
87 if (tile.next) tile.next.prev = tile.prev;
88 else if (tail = tile.prev) tail.next = null;
89 if (tile.prev) tile.prev.next = tile.next;
90 else if (head = tile.next) head.prev = null;
94 for (var tile = tail; n > size; tile = tile.prev) {
96 if (tile.lock) continue;
101 cache.peek = function(c) {
102 return map[[c.zoom, c.column, c.row].join("/")];
105 cache.load = function(c, projection) {
106 var key = [c.zoom, c.column, c.row].join("/"),
110 tile.prev.next = tile.next;
111 if (tile.next) tile.next.prev = tile.prev;
112 else tail = tile.prev;
131 load.call(null, tile, projection);
132 locks[key] = map[key] = tile;
133 if (head) head.prev = tile;
140 cache.unload = function(key) {
141 if (!(key in locks)) return false;
142 var tile = locks[key];
145 if (tile.request && tile.request.abort(false)) remove(tile);
149 cache.locks = function() {
153 cache.size = function(x) {
154 if (!arguments.length) return size;
160 cache.flush = function() {
165 cache.clear = function() {
166 for (var key in map) {
168 if (tile.request) tile.request.abort(false);
169 if (unload) unload(map[key]);
172 tile.element.parentNode.removeChild(tile.element);
184 po.url = function(template) {
189 var max = c.zoom < 0 ? 1 : 1 << c.zoom,
192 column = c.column % max;
193 if (column < 0) column += max;
194 } else if ((column < 0) || (column >= max)) {
197 return template.replace(/{(.)}/g, function(s, v) {
199 case "S": return hosts[(Math.abs(c.zoom) + c.row + column) % hosts.length];
200 case "Z": return c.zoom;
201 case "X": return column;
202 case "Y": return c.row;
204 var nw = po.map.coordinateLocation({row: c.row, column: column, zoom: c.zoom}),
205 se = po.map.coordinateLocation({row: c.row + 1, column: column + 1, zoom: c.zoom}),
206 pn = Math.ceil(Math.log(c.zoom) / Math.LN2);
207 return se.lat.toFixed(pn)
208 + "," + nw.lon.toFixed(pn)
209 + "," + nw.lat.toFixed(pn)
210 + "," + se.lon.toFixed(pn);
217 format.template = function(x) {
218 if (!arguments.length) return template;
223 format.hosts = function(x) {
224 if (!arguments.length) return hosts;
229 format.repeat = function(x) {
230 if (!arguments.length) return repeat;
237 po.dispatch = function(that) {
240 that.on = function(type, handler) {
241 var listeners = types[type] || (types[type] = []);
242 for (var i = 0; i < listeners.length; i++) {
243 if (listeners[i].handler == handler) return that; // already registered
245 listeners.push({handler: handler, on: true});
249 that.off = function(type, handler) {
250 var listeners = types[type];
251 if (listeners) for (var i = 0; i < listeners.length; i++) {
252 var l = listeners[i];
253 if (l.handler == handler) {
255 listeners.splice(i, 1);
262 return function(event) {
263 var listeners = types[event.type];
264 if (!listeners) return;
265 listeners = listeners.slice(); // defensive copy
266 for (var i = 0; i < listeners.length; i++) {
267 var l = listeners[i];
268 if (l.on) l.handler.call(that, event);
272 po.queue = (function() {
273 var queued = [], active = 0, size = 6;
276 if ((active >= size) || !queued.length) return;
281 function dequeue(send) {
282 for (var i = 0; i < queued.length; i++) {
283 if (queued[i] == send) {
291 function request(url, callback, mimeType) {
295 req = new XMLHttpRequest();
297 req.overrideMimeType(mimeType);
299 req.open("GET", url, true);
300 req.onreadystatechange = function(e) {
301 if (req.readyState == 4) {
303 if (req.status < 300) callback(req);
310 function abort(hard) {
311 if (dequeue(send)) return true;
312 if (hard && req) { req.abort(); return true; }
318 return {abort: abort};
321 function text(url, callback, mimeType) {
322 return request(url, function(req) {
323 if (req.responseText) callback(req.responseText);
328 * We the override MIME type here so that you can load local files; some
329 * browsers don't assign a proper MIME type for local files.
332 function json(url, callback) {
333 return request(url, function(req) {
334 if (req.responseText) callback(JSON.parse(req.responseText));
335 }, "application/json");
338 function xml(url, callback) {
339 return request(url, function(req) {
340 if (req.responseXML) callback(req.responseXML);
341 }, "application/xml");
344 function image(image, src, callback) {
348 img = document.createElement("img");
349 img.onerror = function() {
353 img.onload = function() {
359 image.setAttributeNS(po.ns.xlink, "href", src);
362 function abort(hard) {
363 if (dequeue(send)) return true;
364 if (hard && img) { img.src = "about:"; return true; } // cancels request
370 return {abort: abort};
373 return {text: text, xml: xml, json: json, image: image};
375 po.map = function() {
380 sizeRadius = zero, // sizeActual / 2
381 tileSize = {x: 256, y: 256},
382 center = {lat: 37.76487, lon: -122.41948},
385 zoomFactor = 1, // Math.pow(2, zoomFraction)
388 angleCos = 1, // Math.cos(angle)
389 angleSin = 0, // Math.sin(angle)
390 angleCosi = 1, // Math.cos(-angle)
391 angleSini = 0, // Math.sin(-angle)
392 ymin = -180, // lat2y(centerRange[0].lat)
393 ymax = 180; // lat2y(centerRange[1].lat)
396 {lat: y2lat(ymin), lon: -Infinity},
397 {lat: y2lat(ymax), lon: Infinity}
400 map.locationCoordinate = function(l) {
401 var c = po.map.locationCoordinate(l),
402 k = Math.pow(2, zoom);
409 map.coordinateLocation = po.map.coordinateLocation;
411 map.coordinatePoint = function(tileCenter, c) {
412 var kc = Math.pow(2, zoom - c.zoom),
413 kt = Math.pow(2, zoom - tileCenter.zoom),
414 dx = (c.column * kc - tileCenter.column * kt) * tileSize.x * zoomFactor,
415 dy = (c.row * kc - tileCenter.row * kt) * tileSize.y * zoomFactor;
417 x: sizeRadius.x + angleCos * dx - angleSin * dy,
418 y: sizeRadius.y + angleSin * dx + angleCos * dy
422 map.pointCoordinate = function(tileCenter, p) {
423 var kt = Math.pow(2, zoom - tileCenter.zoom),
424 dx = (p.x - sizeRadius.x) / zoomFactor,
425 dy = (p.y - sizeRadius.y) / zoomFactor;
427 column: tileCenter.column * kt + (angleCosi * dx - angleSini * dy) / tileSize.x,
428 row: tileCenter.row * kt + (angleSini * dx + angleCosi * dy) / tileSize.y,
433 map.locationPoint = function(l) {
434 var k = Math.pow(2, zoom + zoomFraction - 3) / 45,
435 dx = (l.lon - center.lon) * k * tileSize.x,
436 dy = (lat2y(center.lat) - lat2y(l.lat)) * k * tileSize.y;
438 x: sizeRadius.x + angleCos * dx - angleSin * dy,
439 y: sizeRadius.y + angleSin * dx + angleCos * dy
443 map.pointLocation = function(p) {
444 var k = 45 / Math.pow(2, zoom + zoomFraction - 3),
445 dx = (p.x - sizeRadius.x) * k,
446 dy = (p.y - sizeRadius.y) * k;
448 lon: center.lon + (angleCosi * dx - angleSini * dy) / tileSize.x,
449 lat: y2lat(lat2y(center.lat) - (angleSini * dx + angleCosi * dy) / tileSize.y)
455 if (zoom < zoomRange[0]) zoom = zoomRange[0];
456 else if (zoom > zoomRange[1]) zoom = zoomRange[1];
458 zoomFraction = zoom - (zoom = Math.round(zoom));
459 zoomFactor = Math.pow(2, zoomFraction);
462 function recenter() {
463 if (!centerRange) return;
464 var k = 45 / Math.pow(2, zoom + zoomFraction - 3);
466 // constrain latitude
467 var y = Math.max(Math.abs(angleSin * sizeRadius.x + angleCos * sizeRadius.y),
468 Math.abs(angleSini * sizeRadius.x + angleCosi * sizeRadius.y)),
469 lat0 = y2lat(ymin - y * k / tileSize.y),
470 lat1 = y2lat(ymax + y * k / tileSize.y);
471 center.lat = Math.max(lat0, Math.min(lat1, center.lat));
473 // constrain longitude
474 var x = Math.max(Math.abs(angleSin * sizeRadius.y + angleCos * sizeRadius.x),
475 Math.abs(angleSini * sizeRadius.y + angleCosi * sizeRadius.x)),
476 lon0 = centerRange[0].lon - x * k / tileSize.x,
477 lon1 = centerRange[1].lon + x * k / tileSize.x;
478 center.lon = Math.max(lon0, Math.min(lon1, center.lon));
481 // a place to capture mouse events if no tiles exist
482 var rect = po.svg("rect");
483 rect.setAttribute("visibility", "hidden");
484 rect.setAttribute("pointer-events", "all");
486 map.container = function(x) {
487 if (!arguments.length) return container;
489 container.setAttribute("class", "map");
490 container.appendChild(rect);
491 return map.resize(); // infer size
494 map.focusableParent = function() {
495 for (var p = container; p; p = p.parentNode) {
496 if (p.tabIndex >= 0) return p;
501 map.mouse = function(e) {
502 var point = (container.ownerSVGElement || container).createSVGPoint();
503 if ((bug44083 < 0) && (window.scrollX || window.scrollY)) {
504 var svg = document.body.appendChild(po.svg("svg"));
505 svg.style.position = "absolute";
506 svg.style.top = svg.style.left = "0px";
507 var ctm = svg.getScreenCTM();
508 bug44083 = !(ctm.f || ctm.e);
509 document.body.removeChild(svg);
518 return point.matrixTransform(container.getScreenCTM().inverse());
521 map.size = function(x) {
522 if (!arguments.length) return sizeActual;
524 return map.resize(); // size tiles
527 map.resize = function() {
529 rect.setAttribute("width", "100%");
530 rect.setAttribute("height", "100%");
532 sizeActual = {x: b.width, y: b.height};
538 rect.setAttribute("width", sizeActual.x);
539 rect.setAttribute("height", sizeActual.y);
540 sizeRadius = {x: sizeActual.x / 2, y: sizeActual.y / 2};
542 map.dispatch({type: "resize"});
546 map.tileSize = function(x) {
547 if (!arguments.length) return tileSize;
549 map.dispatch({type: "move"});
553 map.center = function(x) {
554 if (!arguments.length) return center;
557 map.dispatch({type: "move"});
561 map.panBy = function(x) {
562 var k = 45 / Math.pow(2, zoom + zoomFraction - 3),
566 lon: center.lon + (angleSini * dy - angleCosi * dx) / tileSize.x,
567 lat: y2lat(lat2y(center.lat) + (angleSini * dx + angleCosi * dy) / tileSize.y)
571 map.centerRange = function(x) {
572 if (!arguments.length) return centerRange;
575 ymin = centerRange[0].lat > -90 ? lat2y(centerRange[0].lat) : -Infinity;
576 ymax = centerRange[0].lat < 90 ? lat2y(centerRange[1].lat) : Infinity;
582 map.dispatch({type: "move"});
586 map.zoom = function(x) {
587 if (!arguments.length) return zoom + zoomFraction;
590 return map.center(center);
593 map.zoomBy = function(z, x0, l) {
594 if (arguments.length < 2) return map.zoom(zoom + zoomFraction + z);
596 // compute the location of x0
597 if (arguments.length < 3) l = map.pointLocation(x0);
599 // update the zoom level
600 zoom = zoom + zoomFraction + z;
603 // compute the new point of the location
604 var x1 = map.locationPoint(l);
606 return map.panBy({x: x0.x - x1.x, y: x0.y - x1.y});
609 map.zoomRange = function(x) {
610 if (!arguments.length) return zoomRange;
612 return map.zoom(zoom + zoomFraction);
615 map.extent = function(x) {
616 if (!arguments.length) return [
617 map.pointLocation({x: 0, y: sizeActual.y}),
618 map.pointLocation({x: sizeActual.x, y: 0})
621 // compute the extent in points, scale factor, and center
622 var bl = map.locationPoint(x[0]),
623 tr = map.locationPoint(x[1]),
624 k = Math.max((tr.x - bl.x) / sizeActual.x, (bl.y - tr.y) / sizeActual.y),
625 l = map.pointLocation({x: (bl.x + tr.x) / 2, y: (bl.y + tr.y) / 2});
627 // update the zoom level
628 zoom = zoom + zoomFraction - Math.log(k) / Math.LN2;
631 // set the new center
632 return map.center(l);
635 map.angle = function(x) {
636 if (!arguments.length) return angle;
638 angleCos = Math.cos(angle);
639 angleSin = Math.sin(angle);
640 angleCosi = Math.cos(-angle);
641 angleSini = Math.sin(-angle);
643 map.dispatch({type: "move"});
647 map.add = function(x) {
652 map.remove = function(x) {
657 map.dispatch = po.dispatch(map);
662 function resizer(e) {
663 for (var i = 0; i < resizer.maps.length; i++) {
664 resizer.maps[i].resize();
670 resizer.add = function(map) {
671 for (var i = 0; i < resizer.maps.length; i++) {
672 if (resizer.maps[i] == map) return;
674 resizer.maps.push(map);
677 resizer.remove = function(map) {
678 for (var i = 0; i < resizer.maps.length; i++) {
679 if (resizer.maps[i] == map) {
680 resizer.maps.splice(i, 1);
686 // Note: assumes single window (no frames, iframes, etc.)!
687 window.addEventListener("resize", resizer, false);
688 window.addEventListener("load", resizer, false);
690 // See http://wiki.openstreetmap.org/wiki/Mercator
693 return 360 / Math.PI * Math.atan(Math.exp(y * Math.PI / 180)) - 90;
696 function lat2y(lat) {
697 return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360));
700 po.map.locationCoordinate = function(l) {
703 column: (l.lon + 180) * k,
704 row: (180 - lat2y(l.lat)) * k,
709 po.map.coordinateLocation = function(c) {
710 var k = 45 / Math.pow(2, c.zoom - 3);
712 lon: k * c.column - 180,
713 lat: y2lat(180 - k * c.row)
717 // https://bugs.webkit.org/show_bug.cgi?id=44083
718 var bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0;
719 po.layer = function(load, unload) {
721 cache = layer.cache = po.cache(load, unload).size(512),
727 container = po.svg("g"),
732 container.setAttribute("class", "layer");
733 for (var i = -4; i <= -1; i++) levels[i] = container.appendChild(po.svg("g"));
734 for (var i = 2; i >= 1; i--) levels[i] = container.appendChild(po.svg("g"));
735 levels[0] = container.appendChild(po.svg("g"));
738 var end = levels[0].nextSibling;
739 for (; levelZoom < z; levelZoom++) {
740 // -4, -3, -2, -1, +2, +1, =0 // current order
741 // -3, -2, -1, +2, +1, =0, -4 // insertBefore(-4, end)
742 // -3, -2, -1, +1, =0, -4, +2 // insertBefore(+2, end)
743 // -3, -2, -1, =0, -4, +2, +1 // insertBefore(+1, end)
744 // -4, -3, -2, -1, +2, +1, =0 // relabel
745 container.insertBefore(levels[-4], end);
746 container.insertBefore(levels[2], end);
747 container.insertBefore(levels[1], end);
749 for (var dz = -4; dz < 2;) levels[dz] = levels[++dz];
754 function zoomOut(z) {
755 var end = levels[0].nextSibling;
756 for (; levelZoom > z; levelZoom--) {
757 // -4, -3, -2, -1, +2, +1, =0 // current order
758 // -4, -3, -2, +2, +1, =0, -1 // insertBefore(-1, end)
759 // +2, -4, -3, -2, +1, =0, -1 // insertBefore(+2, -4)
760 // -4, -3, -2, -1, +2, +1, =0 // relabel
761 container.insertBefore(levels[-1], end);
762 container.insertBefore(levels[2], levels[-4]);
764 for (var dz = 2; dz > -4;) levels[dz] = levels[--dz];
770 var map = layer.map(), // in case the layer is removed
771 mapZoom = map.zoom(),
772 mapZoomFraction = mapZoom - (mapZoom = Math.round(mapZoom)),
773 mapSize = map.size(),
774 mapAngle = map.angle(),
775 tileSize = map.tileSize(),
776 tileCenter = map.locationCoordinate(map.center());
778 // set the layer zoom levels
779 if (levelZoom != mapZoom) {
780 if (levelZoom < mapZoom) zoomIn(mapZoom);
781 else if (levelZoom > mapZoom) zoomOut(mapZoom);
782 else levelZoom = mapZoom;
783 for (var z = -4; z <= 2; z++) {
785 l.setAttribute("class", "zoom" + (z < 0 ? "" : "+") + z + " zoom" + (mapZoom + z));
786 l.setAttribute("transform", "scale(" + Math.pow(2, -z) + ")");
790 // set the layer transform
791 container.setAttribute("transform",
792 "translate(" + (mapSize.x / 2) + "," + (mapSize.y / 2) + ")"
793 + (mapAngle ? "rotate(" + mapAngle / Math.PI * 180 + ")" : "")
794 + (mapZoomFraction ? "scale(" + Math.pow(2, mapZoomFraction) + ")" : "")
795 + (transform ? transform.zoomFraction(mapZoomFraction) : ""));
797 // get the coordinates of the four corners
798 var c0 = map.pointCoordinate(tileCenter, zero),
799 c1 = map.pointCoordinate(tileCenter, {x: mapSize.x, y: 0}),
800 c2 = map.pointCoordinate(tileCenter, mapSize),
801 c3 = map.pointCoordinate(tileCenter, {x: 0, y: mapSize.y});
803 // round to pixel boundary to avoid anti-aliasing artifacts
804 if (!mapZoomFraction && !mapAngle && !transform) {
805 tileCenter.column = (Math.round(tileSize.x * tileCenter.column) + (mapSize.x & 1) / 2) / tileSize.x;
806 tileCenter.row = (Math.round(tileSize.y * tileCenter.row) + (mapSize.y & 1) / 2) / tileSize.y;
809 // layer-specific coordinate transform
811 c0 = transform.unapply(c0);
812 c1 = transform.unapply(c1);
813 c2 = transform.unapply(c2);
814 c3 = transform.unapply(c3);
815 tileCenter = transform.unapply(tileCenter);
818 // layer-specific zoom transform
819 var tileLevel = zoom ? zoom(c0.zoom) - c0.zoom : 0;
821 var k = Math.pow(2, tileLevel);
822 c0.column *= k; c0.row *= k;
823 c1.column *= k; c1.row *= k;
824 c2.column *= k; c2.row *= k;
825 c3.column *= k; c3.row *= k;
826 c0.zoom = c1.zoom = c2.zoom = c3.zoom += tileLevel;
829 // tile-specific projection
830 function projection(c) {
832 max = zoom < 0 ? 1 : 1 << zoom,
833 column = c.column % max,
835 if (column < 0) column += max;
837 locationPoint: function(l) {
838 var c = po.map.locationCoordinate(l),
839 k = Math.pow(2, zoom - c.zoom);
841 x: tileSize.x * (k * c.column - column),
842 y: tileSize.y * (k * c.row - row)
848 // record which tiles are visible
849 var oldLocks = cache.locks(), newLocks = {};
851 // reset the proxy counts
852 for (var key in oldLocks) {
853 oldLocks[key].proxyCount = 0;
857 if (visible && tileLevel > -5 && tileLevel < 3) {
858 var ymax = c0.zoom < 0 ? 1 : 1 << c0.zoom;
860 scanTriangle(c0, c1, c2, 0, ymax, scanLine);
861 scanTriangle(c2, c3, c0, 0, ymax, scanLine);
863 var x = Math.floor((c0.column + c2.column) / 2),
864 y = Math.max(0, Math.min(ymax - 1, Math.floor((c1.row + c3.row) / 2))),
865 z = Math.min(4, c0.zoom);
868 scanLine(x, x + 1, y);
872 // scan-line conversion
873 function scanLine(x0, x1, y) {
878 for (var x = x0; x < x1; x++) {
879 var t = cache.load({column: x, row: y, zoom: z}, projection);
880 if (!t.ready && !(t.key in newLocks)) {
884 // downsample high-resolution tiles
885 for (var dz = 1; dz <= z0; dz++) {
887 for (var dy = 0, k = 1 << dz; dy <= k; dy++) {
888 for (var dx = 0; dx <= k; dx++) {
889 proxy = cache.peek(c = {
890 column: (x << dz) + dx,
894 if (proxy && proxy.ready) {
895 newLocks[proxy.key] = cache.load(c);
897 t.proxyRefs[proxy.key] = proxy;
906 // upsample low-resolution tiles
908 for (var dz = 1; dz <= z1; dz++) {
909 proxy = cache.peek(c = {
914 if (proxy && proxy.ready) {
915 newLocks[proxy.key] = cache.load(c);
917 t.proxyRefs[proxy.key] = proxy;
928 for (var key in newLocks) {
929 var t = newLocks[key],
930 k = Math.pow(2, t.level = t.zoom - tileCenter.zoom);
931 t.element.setAttribute("transform", "translate("
932 + (t.x = tileSize.x * (t.column - tileCenter.column * k)) + ","
933 + (t.y = tileSize.y * (t.row - tileCenter.row * k)) + ")");
936 // remove tiles that are no longer visible
937 for (var key in oldLocks) {
938 if (!(key in newLocks)) {
939 var t = cache.unload(key);
940 t.element.parentNode.removeChild(t.element);
945 // append tiles that are now visible
946 for (var key in newLocks) {
947 var t = newLocks[key];
948 if (t.element.parentNode != levels[t.level]) {
949 levels[t.level].appendChild(t.element);
950 if (layer.show) layer.show(t);
954 // flush the cache, clearing no-longer-needed tiles
957 // dispatch the move event
958 layer.dispatch({type: "move"});
961 // remove proxy tiles when tiles load
962 function cleanup(e) {
963 if (e.tile.proxyRefs) {
964 for (var proxyKey in e.tile.proxyRefs) {
965 var proxyTile = e.tile.proxyRefs[proxyKey];
966 if ((--proxyTile.proxyCount <= 0) && cache.unload(proxyKey)) {
967 proxyTile.element.parentNode.removeChild(proxyTile.element);
970 delete e.tile.proxyRefs;
974 layer.map = function(x) {
975 if (!arguments.length) return map;
978 container.parentNode.appendChild(container); // move to end
981 map.off("move", move).off("resize", move);
982 container.parentNode.removeChild(container);
986 map.container().appendChild(container);
987 if (layer.init) layer.init(container);
988 map.on("move", move).on("resize", move);
994 layer.container = function() {
998 layer.levels = function() {
1002 layer.id = function(x) {
1003 if (!arguments.length) return id;
1005 container.setAttribute("id", x);
1009 layer.visible = function(x) {
1010 if (!arguments.length) return visible;
1011 if (visible = x) container.removeAttribute("visibility")
1012 else container.setAttribute("visibility", "hidden");
1017 layer.transform = function(x) {
1018 if (!arguments.length) return transform;
1024 layer.zoom = function(x) {
1025 if (!arguments.length) return zoom;
1026 zoom = typeof x == "function" || x == null ? x : function() { return x; };
1031 layer.tile = function(x) {
1032 if (!arguments.length) return tile;
1038 layer.reload = function() {
1044 layer.dispatch = po.dispatch(layer);
1045 layer.on("load", cleanup);
1050 // scan-line conversion
1051 function edge(a, b) {
1052 if (a.row > b.row) { var t = a; a = b; b = t; }
1058 dx: b.column - a.column,
1063 // scan-line conversion
1064 function scanSpans(e0, e1, ymin, ymax, scanLine) {
1065 var y0 = Math.max(ymin, Math.floor(e1.y0)),
1066 y1 = Math.min(ymax, Math.ceil(e1.y1));
1068 // sort edges by x-coordinate
1069 if ((e0.x0 == e1.x0 && e0.y0 == e1.y0)
1070 ? (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1)
1071 : (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) {
1072 var t = e0; e0 = e1; e1 = t;
1076 var m0 = e0.dx / e0.dy,
1078 d0 = e0.dx > 0, // use y + 1 to compute x0
1079 d1 = e1.dx < 0; // use y + 1 to compute x1
1080 for (var y = y0; y < y1; y++) {
1081 var x0 = m0 * Math.max(0, Math.min(e0.dy, y + d0 - e0.y0)) + e0.x0,
1082 x1 = m1 * Math.max(0, Math.min(e1.dy, y + d1 - e1.y0)) + e1.x0;
1083 scanLine(Math.floor(x1), Math.ceil(x0), y);
1087 // scan-line conversion
1088 function scanTriangle(a, b, c, ymin, ymax, scanLine) {
1089 var ab = edge(a, b),
1093 // sort edges by y-length
1094 if (ab.dy > bc.dy) { var t = ab; ab = bc; bc = t; }
1095 if (ab.dy > ca.dy) { var t = ab; ab = ca; ca = t; }
1096 if (bc.dy > ca.dy) { var t = bc; bc = ca; ca = t; }
1098 // scan span! scan span!
1099 if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine);
1100 if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine);
1102 po.image = function() {
1103 var image = po.layer(load, unload),
1106 function load(tile) {
1107 var element = tile.element = po.svg("image"), size = image.map().tileSize();
1108 element.setAttribute("preserveAspectRatio", "none");
1109 element.setAttribute("width", size.x);
1110 element.setAttribute("height", size.y);
1112 if (typeof url == "function") {
1113 element.setAttribute("opacity", 0);
1114 var tileUrl = url(tile);
1115 if (tileUrl != null) {
1116 tile.request = po.queue.image(element, tileUrl, function(img) {
1117 delete tile.request;
1120 element.removeAttribute("opacity");
1121 image.dispatch({type: "load", tile: tile});
1125 image.dispatch({type: "load", tile: tile});
1129 if (url != null) element.setAttributeNS(po.ns.xlink, "href", url);
1130 image.dispatch({type: "load", tile: tile});
1134 function unload(tile) {
1135 if (tile.request) tile.request.abort(true);
1138 image.url = function(x) {
1139 if (!arguments.length) return url;
1140 url = typeof x == "string" && /{.}/.test(x) ? po.url(x) : x;
1141 return image.reload();
1146 po.geoJson = function(fetch) {
1147 var geoJson = po.layer(load, unload),
1148 container = geoJson.container(),
1151 clipId = "org.polymaps." + po.id(),
1152 clipHref = "url(#" + clipId + ")",
1153 clipPath = container.insertBefore(po.svg("clipPath"), container.firstChild),
1154 clipRect = clipPath.appendChild(po.svg("rect")),
1159 container.setAttribute("fill-rule", "evenodd");
1160 clipPath.setAttribute("id", clipId);
1162 if (!arguments.length) fetch = po.queue.json;
1164 function projection(proj) {
1165 var l = {lat: 0, lon: 0};
1166 return function(coordinates) {
1167 l.lat = coordinates[1];
1168 l.lon = coordinates[0];
1170 coordinates.x = p.x;
1171 coordinates.y = p.y;
1176 function geometry(o, proj) {
1177 return o && o.type in types && types[o.type](o, proj);
1182 Point: function(o, proj) {
1183 var p = proj(o.coordinates),
1184 c = po.svg("circle");
1185 c.setAttribute("r", 4.5);
1186 c.setAttribute("transform", "translate(" + p.x + "," + p.y + ")");
1190 MultiPoint: function(o, proj) {
1191 var g = po.svg("g"),
1198 x = g.appendChild(po.svg("circle"));
1199 x.setAttribute("r", 4.5);
1200 x.setAttribute("transform", "translate(" + (p = proj(c[i])).x + "," + p.y + ")");
1205 LineString: function(o, proj) {
1206 var x = po.svg("path"),
1212 while (++i < n) d.push((p = proj(c[i])).x, ",", p.y, "L");
1214 if (!d.length) return;
1215 x.setAttribute("d", d.join(""));
1219 MultiLineString: function(o, proj) {
1220 var x = po.svg("path"),
1233 while (++j < m) d.push((p = proj(cj[j])).x, ",", p.y, "L");
1236 if (!d.length) return;
1237 x.setAttribute("d", d.join(""));
1241 Polygon: function(o, proj) {
1242 var x = po.svg("path"),
1255 while (++j < m) d.push((p = proj(cj[j])).x, ",", p.y, "L");
1256 d[d.length - 1] = "Z";
1258 if (!d.length) return;
1259 x.setAttribute("d", d.join(""));
1263 MultiPolygon: function(o, proj) {
1264 var x = po.svg("path"),
1284 while (++k < l) d.push((p = proj(ck[k])).x, ",", p.y, "L");
1285 d[d.length - 1] = "Z";
1288 if (!d.length) return;
1289 x.setAttribute("d", d.join(""));
1293 GeometryCollection: function(o, proj) {
1294 var g = po.svg("g"),
1300 x = geometry(c[i], proj);
1301 if (x) g.appendChild(x);
1308 function rescale(o, e, k) {
1309 return o.type in rescales && rescales[o.type](o, e, k);
1314 Point: function (o, e, k) {
1315 var p = o.coordinates;
1316 e.setAttribute("transform", "translate(" + p.x + "," + p.y + ")" + k);
1319 MultiPoint: function (o, e, k) {
1320 var c = o.coordinates,
1327 x.setAttribute("transform", "translate(" + p.x + "," + p.y + ")" + k);
1334 function load(tile, proj) {
1335 var g = tile.element = po.svg("g");
1338 proj = projection(proj(tile).locationPoint);
1340 function update(data) {
1343 /* Fetch the next batch of features, if so directed. */
1344 if (data.next) tile.request = fetch(data.next.href, update);
1346 /* Convert the GeoJSON to SVG. */
1347 switch (data.type) {
1348 case "FeatureCollection": {
1349 for (var i = 0; i < data.features.length; i++) {
1350 var feature = data.features[i],
1351 element = geometry(feature.geometry, proj);
1352 if (element) updated.push({element: g.appendChild(element), data: feature});
1357 var element = geometry(data.geometry, proj);
1358 if (element) updated.push({element: g.appendChild(element), data: data});
1362 var element = geometry(data, proj);
1363 if (element) updated.push({element: g.appendChild(element), data: {type: "Feature", geometry: data}});
1369 updated.push.apply(tile.features, updated);
1370 geoJson.dispatch({type: "load", tile: tile, features: updated});
1374 tile.request = fetch(typeof url == "function" ? url(tile) : url, update);
1376 update({type: "FeatureCollection", features: features || []});
1380 function unload(tile) {
1381 if (tile.request) tile.request.abort(true);
1385 var zoom = geoJson.map().zoom(),
1386 tiles = geoJson.cache.locks(), // visible tiles
1387 key, // key in locks
1389 features, // tile.features
1390 i, // current feature index
1391 n, // current feature count, features.length
1392 feature, // features[i]
1393 k; // scale transform
1394 if (scale == "fixed") {
1395 for (key in tiles) {
1396 if ((tile = tiles[key]).scale != zoom) {
1397 k = "scale(" + Math.pow(2, tile.zoom - zoom) + ")";
1399 n = (features = tile.features).length;
1400 while (++i < n) rescale((feature = features[i]).data.geometry, feature.element, k);
1405 for (key in tiles) {
1407 n = (features = (tile = tiles[key]).features).length;
1408 while (++i < n) rescale((feature = features[i]).data.geometry, feature.element, "");
1414 geoJson.url = function(x) {
1415 if (!arguments.length) return url;
1416 url = typeof x == "string" && /{.}/.test(x) ? po.url(x) : x;
1417 if (url != null) features = null;
1418 if (typeof url == "string") geoJson.tile(false);
1419 return geoJson.reload();
1422 geoJson.features = function(x) {
1423 if (!arguments.length) return features;
1426 geoJson.tile(false);
1428 return geoJson.reload();
1431 geoJson.clip = function(x) {
1432 if (!arguments.length) return clip;
1433 if (clip) container.removeChild(clipPath);
1434 if (clip = x) container.insertBefore(clipPath, container.firstChild);
1435 var locks = geoJson.cache.locks();
1436 for (var key in locks) {
1437 if (clip) locks[key].element.setAttribute("clip-path", clipHref);
1438 else locks[key].element.removeAttribute("clip-path");
1443 var __tile__ = geoJson.tile;
1444 geoJson.tile = function(x) {
1445 if (arguments.length && !x) geoJson.clip(x);
1446 return __tile__.apply(geoJson, arguments);
1449 var __map__ = geoJson.map;
1450 geoJson.map = function(x) {
1451 if (x && clipRect) {
1452 var size = x.tileSize();
1453 clipRect.setAttribute("width", size.x);
1454 clipRect.setAttribute("height", size.y);
1456 return __map__.apply(geoJson, arguments);
1459 geoJson.scale = function(x) {
1460 if (!arguments.length) return scale;
1461 if (scale = x) geoJson.on("move", move);
1462 else geoJson.off("move", move);
1463 if (geoJson.map()) move();
1467 geoJson.show = function(tile) {
1468 if (clip) tile.element.setAttribute("clip-path", clipHref);
1469 else tile.element.removeAttribute("clip-path");
1470 geoJson.dispatch({type: "show", tile: tile, features: tile.features});
1474 geoJson.reshow = function() {
1475 var locks = geoJson.cache.locks();
1476 for (var key in locks) geoJson.show(locks[key]);
1482 po.dblclick = function() {
1488 function handle(e) {
1490 if (e.shiftKey) z = Math.ceil(z) - z - 1;
1491 else z = 1 - z + Math.floor(z);
1492 zoom === "mouse" ? map.zoomBy(z, map.mouse(e)) : map.zoomBy(z);
1495 dblclick.zoom = function(x) {
1496 if (!arguments.length) return zoom;
1501 dblclick.map = function(x) {
1502 if (!arguments.length) return map;
1504 container.removeEventListener("dblclick", handle, false);
1508 container = map.container();
1509 container.addEventListener("dblclick", handle, false);
1516 po.drag = function() {
1522 function mousedown(e) {
1523 if (e.shiftKey) return;
1528 map.focusableParent().focus();
1530 document.body.style.setProperty("cursor", "move", null);
1533 function mousemove(e) {
1534 if (!dragging) return;
1535 map.panBy({x: e.clientX - dragging.x, y: e.clientY - dragging.y});
1536 dragging.x = e.clientX;
1537 dragging.y = e.clientY;
1540 function mouseup(e) {
1541 if (!dragging) return;
1544 document.body.style.removeProperty("cursor");
1547 drag.map = function(x) {
1548 if (!arguments.length) return map;
1550 container.removeEventListener("mousedown", mousedown, false);
1554 container = map.container();
1555 container.addEventListener("mousedown", mousedown, false);
1560 window.addEventListener("mousemove", mousemove, false);
1561 window.addEventListener("mouseup", mouseup, false);
1565 po.wheel = function() {
1579 // mousewheel events are totally broken!
1580 // https://bugs.webkit.org/show_bug.cgi?id=40441
1581 // not only that, but Chrome and Safari differ in re. to acceleration!
1582 var inner = document.createElement("div"),
1583 outer = document.createElement("div");
1584 outer.style.visibility = "hidden";
1585 outer.style.top = "0px";
1586 outer.style.height = "0px";
1587 outer.style.width = "0px";
1588 outer.style.overflowY = "scroll";
1589 inner.style.height = "2000px";
1590 outer.appendChild(inner);
1591 document.body.appendChild(outer);
1593 function mousewheel(e) {
1594 var delta = e.wheelDelta || -e.detail,
1597 /* Detect the pixels that would be scrolled by this wheel event. */
1601 outer.scrollTop = 1000;
1602 outer.dispatchEvent(e);
1603 delta = 1000 - outer.scrollTop;
1605 // Derp! Hope for the best?
1610 /* If smooth zooming is disabled, batch events into unit steps. */
1612 var timeNow = Date.now();
1613 if (timeNow - timePrev > 200) {
1614 delta = delta > 0 ? +1 : -1;
1625 point = map.mouse(e);
1626 if (!location) location = map.pointLocation(point);
1627 map.off("move", move).zoomBy(delta, point, location).on("move", move);
1631 map.zoomBy(delta, map.locationPoint(location), location);
1634 default: { // center
1642 return false; // for Firefox
1645 wheel.smooth = function(x) {
1646 if (!arguments.length) return smooth;
1651 wheel.zoom = function(x, l) {
1652 if (!arguments.length) return zoom;
1656 if (zoom == "mouse") map.on("move", move);
1657 else map.off("move", move);
1662 wheel.map = function(x) {
1663 if (!arguments.length) return map;
1665 container.removeEventListener("mousemove", move, false);
1666 container.removeEventListener("mousewheel", mousewheel, false);
1667 container.removeEventListener("MozMousePixelScroll", mousewheel, false);
1669 map.off("move", move);
1672 if (zoom == "mouse") map.on("move", move);
1673 container = map.container();
1674 container.addEventListener("mousemove", move, false);
1675 container.addEventListener("mousewheel", mousewheel, false);
1676 container.addEventListener("MozMousePixelScroll", mousewheel, false);
1683 po.arrow = function() {
1685 key = {left: 0, right: 0, up: 0, down: 0},
1689 repeatInterval = 50,
1694 function keydown(e) {
1695 if (e.ctrlKey || e.altKey || e.metaKey) return;
1696 var now = Date.now(), dx = 0, dy = 0;
1697 switch (e.keyCode) {
1702 if (!key.right) dx = speed;
1710 if (!key.left) dx = -speed;
1718 if (!key.down) dy = speed;
1726 if (!key.up) dy = -speed;
1732 if (dx || dy) map.panBy({x: dx, y: dy});
1733 if (!repeatTimer && (key.left | key.right | key.up | key.down)) {
1734 repeatTimer = setInterval(repeat, repeatInterval);
1741 switch (e.keyCode) {
1742 case 37: key.left = 0; break;
1743 case 39: key.right = 0; break;
1744 case 38: key.up = 0; break;
1745 case 40: key.down = 0; break;
1748 if (repeatTimer && !(key.left | key.right | key.up | key.down)) {
1749 repeatTimer = clearInterval(repeatTimer);
1754 function keypress(e) {
1755 switch (e.charCode) {
1756 case 45: case 95: map.zoom(Math.ceil(map.zoom()) - 1); break; // - _
1757 case 43: case 61: map.zoom(Math.floor(map.zoom()) + 1); break; // = +
1765 if (Date.now() < last + repeatDelay) return;
1766 var dx = (key.left - key.right) * speed,
1767 dy = (key.up - key.down) * speed;
1768 if (dx || dy) map.panBy({x: dx, y: dy});
1771 arrow.map = function(x) {
1772 if (!arguments.length) return map;
1774 parent.removeEventListener("keypress", keypress, false);
1775 parent.removeEventListener("keydown", keydown, false);
1776 parent.removeEventListener("keyup", keyup, false);
1780 parent = map.focusableParent();
1781 parent.addEventListener("keypress", keypress, false);
1782 parent.addEventListener("keydown", keydown, false);
1783 parent.addEventListener("keyup", keyup, false);
1788 arrow.speed = function(x) {
1789 if (!arguments.length) return speed;
1796 po.hash = function() {
1798 s0, // cached location.hash
1799 lat = 90 - 1e-8, // allowable latitude range
1802 var parser = function(map, s) {
1803 var args = s.split("/").map(Number);
1804 if (args.length < 3 || args.some(isNaN)) return true; // replace bogus hash
1806 var size = map.size();
1807 map.zoomBy(args[0] - map.zoom(),
1808 {x: size.x / 2, y: size.y / 2},
1809 {lat: Math.min(lat, Math.max(-lat, args[1])), lon: args[2]});
1813 var formatter = function(map) {
1814 var center = map.center(),
1816 precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
1817 return "#" + zoom.toFixed(2)
1818 + "/" + center.lat.toFixed(precision)
1819 + "/" + center.lon.toFixed(precision);
1823 var s1 = formatter(map);
1824 if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map!
1827 function hashchange() {
1828 if (location.hash === s0) return; // ignore spurious hashchange events
1829 if (parser(map, (s0 = location.hash).substring(1)))
1830 move(); // replace bogus hash
1833 hash.map = function(x) {
1834 if (!arguments.length) return map;
1836 map.off("move", move);
1837 window.removeEventListener("hashchange", hashchange, false);
1840 map.on("move", move);
1841 window.addEventListener("hashchange", hashchange, false);
1842 location.hash ? hashchange() : move();
1847 hash.parser = function(x) {
1848 if (!arguments.length) return parser;
1853 hash.formatter = function(x) {
1854 if (!arguments.length) return formatter;
1861 po.touch = function() {
1869 locations = {}; // touch identifier -> location
1871 window.addEventListener("touchmove", touchmove, false);
1873 function touchstart(e) {
1875 n = e.touches.length,
1878 // doubletap detection
1879 if ((n == 1) && (t - last < 300)) {
1881 map.zoomBy(1 - z + Math.floor(z), map.mouse(e.touches[0]));
1886 // store original zoom & touch locations
1888 angle = map.angle();
1891 locations[t.identifier] = map.pointLocation(map.mouse(t));
1895 function touchmove(e) {
1896 switch (e.touches.length) {
1897 case 1: { // single-touch pan
1898 var t0 = e.touches[0];
1899 map.zoomBy(0, map.mouse(t0), locations[t0.identifier]);
1903 case 2: { // double-touch pan + zoom + rotate
1904 var t0 = e.touches[0],
1908 p2 = {x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2}, // center point
1909 c0 = po.map.locationCoordinate(locations[t0.identifier]),
1910 c1 = po.map.locationCoordinate(locations[t1.identifier]),
1911 c2 = {row: (c0.row + c1.row) / 2, column: (c0.column + c1.column) / 2, zoom: 0},
1912 l2 = po.map.coordinateLocation(c2); // center location
1913 map.zoomBy(Math.log(e.scale) / Math.LN2 + zoom - map.zoom(), p2, l2);
1914 if (rotate) map.angle(e.rotation / 180 * Math.PI + angle);
1921 touch.rotate = function(x) {
1922 if (!arguments.length) return rotate;
1927 touch.map = function(x) {
1928 if (!arguments.length) return map;
1930 container.removeEventListener("touchstart", touchstart, false);
1934 container = map.container();
1935 container.addEventListener("touchstart", touchstart, false);
1942 // Default map controls.
1943 po.interact = function() {
1947 dblclick = po.dblclick(),
1951 interact.map = function(x) {
1962 po.compass = function() {
1970 repeatInterval = 50,
1971 position = "top-left", // top-left, top-right, bottom-left, bottom-right
1972 zoomStyle = "small", // none, small, big
1974 panStyle = "small", // none, small
1979 dragRect = po.svg("rect"),
1984 g.setAttribute("class", "compass");
1985 dragRect.setAttribute("class", "back fore");
1986 dragRect.setAttribute("pointer-events", "none");
1987 dragRect.setAttribute("display", "none");
1989 function panStart(e) {
1990 g.setAttribute("class", "compass active");
1991 if (!panTimer) panTimer = setInterval(panRepeat, repeatInterval);
1992 if (panDirection) map.panBy(panDirection);
1997 function panRepeat() {
1998 if (panDirection && (Date.now() > last + repeatDelay)) {
1999 map.panBy(panDirection);
2003 function mousedown(e) {
2005 drag = {x0: map.mouse(e)};
2006 map.focusableParent().focus();
2011 function mousemove(e) {
2013 drag.x1 = map.mouse(e);
2014 dragRect.setAttribute("x", Math.min(drag.x0.x, drag.x1.x));
2015 dragRect.setAttribute("y", Math.min(drag.x0.y, drag.x1.y));
2016 dragRect.setAttribute("width", Math.abs(drag.x0.x - drag.x1.x));
2017 dragRect.setAttribute("height", Math.abs(drag.x0.y - drag.x1.y));
2018 dragRect.removeAttribute("display");
2021 function mouseup(e) {
2022 g.setAttribute("class", "compass");
2027 x: Math.min(drag.x0.x, drag.x1.x),
2028 y: Math.max(drag.x0.y, drag.x1.y)
2031 x: Math.max(drag.x0.x, drag.x1.x),
2032 y: Math.min(drag.x0.y, drag.x1.y)
2035 dragRect.setAttribute("display", "none");
2040 clearInterval(panTimer);
2047 x ? this.setAttribute("class", "active") : this.removeAttribute("class");
2052 function zoomBy(x) {
2053 return function(e) {
2054 g.setAttribute("class", "compass active");
2056 map.zoom(x < 0 ? Math.ceil(z) - 1 : Math.floor(z) + 1);
2061 function zoomTo(x) {
2062 return function(e) {
2068 function zoomOver() {
2069 this.setAttribute("class", "active");
2072 function zoomOut() {
2073 this.removeAttribute("class");
2076 function cancel(e) {
2077 e.stopPropagation();
2083 var x = Math.SQRT1_2 * r,
2087 dir = g.appendChild(po.svg("path")),
2088 chv = g.appendChild(po.svg("path"));
2089 dir.setAttribute("class", "direction");
2090 dir.setAttribute("pointer-events", "all");
2091 dir.setAttribute("d", "M0,0L" + x + "," + x + "A" + r + "," + r + " 0 0,1 " + -x + "," + x + "Z");
2092 chv.setAttribute("class", "chevron");
2093 chv.setAttribute("d", "M" + z + "," + (y - z) + "L0," + y + " " + -z + "," + (y - z));
2094 chv.setAttribute("pointer-events", "none");
2095 g.addEventListener("mousedown", panStart, false);
2096 g.addEventListener("mouseover", panBy(by), false);
2097 g.addEventListener("mouseout", panBy(null), false);
2098 g.addEventListener("dblclick", cancel, false);
2106 back = g.appendChild(po.svg("path")),
2107 dire = g.appendChild(po.svg("path")),
2108 chev = g.appendChild(po.svg("path")),
2109 fore = g.appendChild(po.svg("path"));
2110 back.setAttribute("class", "back");
2111 back.setAttribute("d", "M" + -x + ",0V" + -x + "A" + x + "," + x + " 0 1,1 " + x + "," + -x + "V0Z");
2112 dire.setAttribute("class", "direction");
2113 dire.setAttribute("d", back.getAttribute("d"));
2114 chev.setAttribute("class", "chevron");
2115 chev.setAttribute("d", "M" + -y + "," + -x + "H" + y + (by > 0 ? "M0," + (-x - y) + "V" + -y : ""));
2116 fore.setAttribute("class", "fore");
2117 fore.setAttribute("fill", "none");
2118 fore.setAttribute("d", back.getAttribute("d"));
2119 g.addEventListener("mousedown", zoomBy(by), false);
2120 g.addEventListener("mouseover", zoomOver, false);
2121 g.addEventListener("mouseout", zoomOut, false);
2122 g.addEventListener("dblclick", cancel, false);
2130 back = g.appendChild(po.svg("rect")),
2131 chev = g.appendChild(po.svg("path"));
2132 back.setAttribute("pointer-events", "all");
2133 back.setAttribute("fill", "none");
2134 back.setAttribute("x", -y);
2135 back.setAttribute("y", -.75 * y);
2136 back.setAttribute("width", 2 * y);
2137 back.setAttribute("height", 1.5 * y);
2138 chev.setAttribute("class", "chevron");
2139 chev.setAttribute("d", "M" + -x + ",0H" + x);
2140 g.addEventListener("mousedown", zoomTo(i), false);
2141 g.addEventListener("dblclick", cancel, false);
2146 var x = r + 6, y = x, size = map.size();
2148 case "top-left": break;
2149 case "top-right": x = size.x - x; break;
2150 case "bottom-left": y = size.y - y; break;
2151 case "bottom-right": x = size.x - x; y = size.y - y; break;
2153 g.setAttribute("transform", "translate(" + x + "," + y + ")");
2154 dragRect.setAttribute("transform", "translate(" + -x + "," + -y + ")");
2155 for (var i in ticks) {
2157 ? ticks[i].setAttribute("class", "active")
2158 : ticks[i].removeAttribute("class");
2163 while (g.lastChild) g.removeChild(g.lastChild);
2165 g.appendChild(dragRect);
2167 if (panStyle != "none") {
2168 panContainer = g.appendChild(po.svg("g"));
2169 panContainer.setAttribute("class", "pan");
2171 var back = panContainer.appendChild(po.svg("circle"));
2172 back.setAttribute("class", "back");
2173 back.setAttribute("r", r);
2175 var s = panContainer.appendChild(pan({x: 0, y: -speed}));
2176 s.setAttribute("transform", "rotate(0)");
2178 var w = panContainer.appendChild(pan({x: speed, y: 0}));
2179 w.setAttribute("transform", "rotate(90)");
2181 var n = panContainer.appendChild(pan({x: 0, y: speed}));
2182 n.setAttribute("transform", "rotate(180)");
2184 var e = panContainer.appendChild(pan({x: -speed, y: 0}));
2185 e.setAttribute("transform", "rotate(270)");
2187 var fore = panContainer.appendChild(po.svg("circle"));
2188 fore.setAttribute("fill", "none");
2189 fore.setAttribute("class", "fore");
2190 fore.setAttribute("r", r);
2192 panContainer = null;
2195 if (zoomStyle != "none") {
2196 zoomContainer = g.appendChild(po.svg("g"));
2197 zoomContainer.setAttribute("class", "zoom");
2200 if (zoomStyle == "big") {
2202 for (var i = map.zoomRange()[0], j = 0; i <= map.zoomRange()[1]; i++, j++) {
2203 (ticks[i] = zoomContainer.appendChild(tick(i)))
2204 .setAttribute("transform", "translate(0," + (-(j + .75) * r * .4) + ")");
2208 var p = panStyle == "none" ? .4 : 2;
2209 zoomContainer.setAttribute("transform", "translate(0," + r * (/^top-/.test(position) ? (p + (j + .5) * .4) : -p) + ")");
2210 zoomContainer.appendChild(zoom(+1)).setAttribute("transform", "translate(0," + (-(j + .5) * r * .4) + ")");
2211 zoomContainer.appendChild(zoom(-1)).setAttribute("transform", "scale(-1)");
2213 zoomContainer = null;
2219 compass.radius = function(x) {
2220 if (!arguments.length) return r;
2226 compass.speed = function(x) {
2227 if (!arguments.length) return r;
2232 compass.position = function(x) {
2233 if (!arguments.length) return position;
2239 compass.pan = function(x) {
2240 if (!arguments.length) return panStyle;
2246 compass.zoom = function(x) {
2247 if (!arguments.length) return zoomStyle;
2253 compass.map = function(x) {
2254 if (!arguments.length) return map;
2256 container.removeEventListener("mousedown", mousedown, false);
2257 container.removeChild(g);
2259 window.removeEventListener("mousemove", mousemove, false);
2260 window.removeEventListener("mouseup", mouseup, false);
2262 map.off("move", move).off("resize", move);
2265 container = map.container();
2266 container.appendChild(g);
2267 container.addEventListener("mousedown", mousedown, false);
2268 window = container.ownerDocument.defaultView;
2269 window.addEventListener("mousemove", mousemove, false);
2270 window.addEventListener("mouseup", mouseup, false);
2271 map.on("move", move).on("resize", move);
2279 po.grid = function() {
2284 g.setAttribute("class", "grid");
2288 line = g.firstChild,
2290 nw = map.pointLocation(zero),
2291 se = map.pointLocation(size),
2292 step = Math.pow(2, 4 - Math.round(map.zoom()));
2295 nw.lat = Math.floor(nw.lat / step) * step;
2296 nw.lon = Math.ceil(nw.lon / step) * step;
2299 for (var x; (x = map.locationPoint(nw).x) <= size.x; nw.lon += step) {
2300 if (!line) line = g.appendChild(po.svg("line"));
2301 line.setAttribute("x1", x);
2302 line.setAttribute("x2", x);
2303 line.setAttribute("y1", 0);
2304 line.setAttribute("y2", size.y);
2305 line = line.nextSibling;
2309 for (var y; (y = map.locationPoint(nw).y) <= size.y; nw.lat -= step) {
2310 if (!line) line = g.appendChild(po.svg("line"));
2311 line.setAttribute("y1", y);
2312 line.setAttribute("y2", y);
2313 line.setAttribute("x1", 0);
2314 line.setAttribute("x2", size.x);
2315 line = line.nextSibling;
2318 // Remove extra ticks.
2320 var next = line.nextSibling;
2321 g.removeChild(line);
2326 grid.map = function(x) {
2327 if (!arguments.length) return map;
2329 g.parentNode.removeChild(g);
2330 map.off("move", move).off("resize", move);
2333 map.on("move", move).on("resize", move);
2334 map.container().appendChild(g);
2335 map.dispatch({type: "move"});
2342 po.stylist = function() {
2347 function stylist(e) {
2348 var ne = e.features.length,
2354 x, // attr or style or title descriptor
2355 v, // attr or style or title value
2358 for (i = 0; i < ne; ++i) {
2359 if (!(o = (f = e.features[i]).element)) continue;
2361 for (j = 0; j < na; ++j) {
2362 v = (x = attrs[j]).value;
2363 if (typeof v === "function") v = v.call(null, d);
2364 v == null ? (x.name.local
2365 ? o.removeAttributeNS(x.name.space, x.name.local)
2366 : o.removeAttribute(x.name)) : (x.name.local
2367 ? o.setAttributeNS(x.name.space, x.name.local, v)
2368 : o.setAttribute(x.name, v));
2370 for (j = 0; j < ns; ++j) {
2371 v = (x = styles[j]).value;
2372 if (typeof v === "function") v = v.call(null, d);
2374 ? o.style.removeProperty(x.name)
2375 : o.style.setProperty(x.name, v, x.priority);
2378 if (typeof v === "function") v = v.call(null, d);
2379 while (o.lastChild) o.removeChild(o.lastChild);
2380 if (v != null) o.appendChild(po.svg("title")).appendChild(document.createTextNode(v));
2385 stylist.attr = function(n, v) {
2386 attrs.push({name: ns(n), value: v});
2390 stylist.style = function(n, v, p) {
2391 styles.push({name: n, value: v, priority: arguments.length < 3 ? null : p});
2395 stylist.title = function(v) {