if (!org) var org = {}; if (!org.polymaps) org.polymaps = {}; (function(po){ po.version = "2.5.1"; // semver.org var zero = {x: 0, y: 0}; po.ns = { svg: "http://www.w3.org/2000/svg", xlink: "http://www.w3.org/1999/xlink" }; function ns(name) { var i = name.indexOf(":"); return i < 0 ? name : { space: po.ns[name.substring(0, i)], local: name.substring(i + 1) }; } po.id = (function() { var id = 0; return function() { return ++id; }; })(); po.svg = function(type) { return document.createElementNS(po.ns.svg, type); }; po.transform = function(a, b, c, d, e, f) { var transform = {}, zoomDelta, zoomFraction, k; if (!arguments.length) { a = 1; c = 0; e = 0; b = 0; d = 1; f = 0; } transform.zoomFraction = function(x) { if (!arguments.length) return zoomFraction; zoomFraction = x; zoomDelta = Math.floor(zoomFraction + Math.log(Math.sqrt(a * a + b * b + c * c + d * d)) / Math.LN2); k = Math.pow(2, -zoomDelta); return transform; }; transform.apply = function(x) { var k0 = Math.pow(2, -x.zoom), k1 = Math.pow(2, x.zoom - zoomDelta); return { column: (a * x.column * k0 + c * x.row * k0 + e) * k1, row: (b * x.column * k0 + d * x.row * k0 + f) * k1, zoom: x.zoom - zoomDelta }; }; transform.unapply = function(x) { var k0 = Math.pow(2, -x.zoom), k1 = Math.pow(2, x.zoom + zoomDelta); return { column: (x.column * k0 * d - x.row * k0 * c - e * d + f * c) / (a * d - b * c) * k1, row: (x.column * k0 * b - x.row * k0 * a - e * b + f * a) / (c * b - d * a) * k1, zoom: x.zoom + zoomDelta }; }; transform.toString = function() { return "matrix(" + [a * k, b * k, c * k, d * k].join(" ") + " 0 0)"; }; return transform.zoomFraction(0); }; po.cache = function(load, unload) { var cache = {}, locks = {}, map = {}, head = null, tail = null, size = 64, n = 0; function remove(tile) { n--; if (unload) unload(tile); delete map[tile.key]; if (tile.next) tile.next.prev = tile.prev; else if (tail = tile.prev) tail.next = null; if (tile.prev) tile.prev.next = tile.next; else if (head = tile.next) head.prev = null; } function flush() { for (var tile = tail; n > size; tile = tile.prev) { if (!tile) break; if (tile.lock) continue; remove(tile); } } cache.peek = function(c) { return map[[c.zoom, c.column, c.row].join("/")]; }; cache.load = function(c, projection) { var key = [c.zoom, c.column, c.row].join("/"), tile = map[key]; if (tile) { if (tile.prev) { tile.prev.next = tile.next; if (tile.next) tile.next.prev = tile.prev; else tail = tile.prev; tile.prev = null; tile.next = head; head.prev = tile; head = tile; } tile.lock = 1; locks[key] = tile; return tile; } tile = { key: key, column: c.column, row: c.row, zoom: c.zoom, next: head, prev: null, lock: 1 }; load.call(null, tile, projection); locks[key] = map[key] = tile; if (head) head.prev = tile; else tail = tile; head = tile; n++; return tile; }; cache.unload = function(key) { if (!(key in locks)) return false; var tile = locks[key]; tile.lock = 0; delete locks[key]; if (tile.request && tile.request.abort(false)) remove(tile); return tile; }; cache.locks = function() { return locks; }; cache.size = function(x) { if (!arguments.length) return size; size = x; flush(); return cache; }; cache.flush = function() { flush(); return cache; }; cache.clear = function() { for (var key in map) { var tile = map[key]; if (tile.request) tile.request.abort(false); if (unload) unload(map[key]); if (tile.lock) { tile.lock = 0; tile.element.parentNode.removeChild(tile.element); } } locks = {}; map = {}; head = tail = null; n = 0; return cache; }; return cache; }; po.url = function(template) { var hosts = [], repeat = true; function format(c) { var max = c.zoom < 0 ? 1 : 1 << c.zoom, column = c.column; if (repeat) { column = c.column % max; if (column < 0) column += max; } else if ((column < 0) || (column >= max)) { return null; } return template.replace(/{(.)}/g, function(s, v) { switch (v) { case "S": return hosts[(Math.abs(c.zoom) + c.row + column) % hosts.length]; case "Z": return c.zoom; case "X": return column; case "Y": return c.row; case "B": { var nw = po.map.coordinateLocation({row: c.row, column: column, zoom: c.zoom}), se = po.map.coordinateLocation({row: c.row + 1, column: column + 1, zoom: c.zoom}), pn = Math.ceil(Math.log(c.zoom) / Math.LN2); return se.lat.toFixed(pn) + "," + nw.lon.toFixed(pn) + "," + nw.lat.toFixed(pn) + "," + se.lon.toFixed(pn); } } return v; }); } format.template = function(x) { if (!arguments.length) return template; template = x; return format; }; format.hosts = function(x) { if (!arguments.length) return hosts; hosts = x; return format; }; format.repeat = function(x) { if (!arguments.length) return repeat; repeat = x; return format; }; return format; }; po.dispatch = function(that) { var types = {}; that.on = function(type, handler) { var listeners = types[type] || (types[type] = []); for (var i = 0; i < listeners.length; i++) { if (listeners[i].handler == handler) return that; // already registered } listeners.push({handler: handler, on: true}); return that; }; that.off = function(type, handler) { var listeners = types[type]; if (listeners) for (var i = 0; i < listeners.length; i++) { var l = listeners[i]; if (l.handler == handler) { l.on = false; listeners.splice(i, 1); break; } } return that; }; return function(event) { var listeners = types[event.type]; if (!listeners) return; listeners = listeners.slice(); // defensive copy for (var i = 0; i < listeners.length; i++) { var l = listeners[i]; if (l.on) l.handler.call(that, event); } }; }; po.queue = (function() { var queued = [], active = 0, size = 6; function process() { if ((active >= size) || !queued.length) return; active++; queued.pop()(); } function dequeue(send) { for (var i = 0; i < queued.length; i++) { if (queued[i] == send) { queued.splice(i, 1); return true; } } return false; } function request(url, callback, mimeType) { var req; function send() { req = new XMLHttpRequest(); if (mimeType && req.overrideMimeType) { req.overrideMimeType(mimeType); } req.open("GET", url, true); req.onreadystatechange = function(e) { if (req.readyState == 4) { active--; if (req.status < 300) callback(req); process(); } }; req.send(null); } function abort(hard) { if (dequeue(send)) return true; if (hard && req) { req.abort(); return true; } return false; } queued.push(send); process(); return {abort: abort}; } function text(url, callback, mimeType) { return request(url, function(req) { if (req.responseText) callback(req.responseText); }, mimeType); } /* * We the override MIME type here so that you can load local files; some * browsers don't assign a proper MIME type for local files. */ function json(url, callback) { return request(url, function(req) { if (req.responseText) callback(JSON.parse(req.responseText)); }, "application/json"); } function xml(url, callback) { return request(url, function(req) { if (req.responseXML) callback(req.responseXML); }, "application/xml"); } function image(image, src, callback) { var img; function send() { img = document.createElement("img"); img.onerror = function() { active--; process(); }; img.onload = function() { active--; callback(img); process(); }; img.src = src; image.setAttributeNS(po.ns.xlink, "href", src); } function abort(hard) { if (dequeue(send)) return true; if (hard && img) { img.src = "about:"; return true; } // cancels request return false; } queued.push(send); process(); return {abort: abort}; } return {text: text, xml: xml, json: json, image: image}; })(); po.map = function() { var map = {}, container, size, sizeActual = zero, sizeRadius = zero, // sizeActual / 2 tileSize = {x: 256, y: 256}, center = {lat: 37.76487, lon: -122.41948}, zoom = 12, zoomFraction = 0, zoomFactor = 1, // Math.pow(2, zoomFraction) zoomRange = [1, 18], angle = 0, angleCos = 1, // Math.cos(angle) angleSin = 0, // Math.sin(angle) angleCosi = 1, // Math.cos(-angle) angleSini = 0, // Math.sin(-angle) ymin = -180, // lat2y(centerRange[0].lat) ymax = 180; // lat2y(centerRange[1].lat) var centerRange = [ {lat: y2lat(ymin), lon: -Infinity}, {lat: y2lat(ymax), lon: Infinity} ]; map.locationCoordinate = function(l) { var c = po.map.locationCoordinate(l), k = Math.pow(2, zoom); c.column *= k; c.row *= k; c.zoom += zoom; return c; }; map.coordinateLocation = po.map.coordinateLocation; map.coordinatePoint = function(tileCenter, c) { var kc = Math.pow(2, zoom - c.zoom), kt = Math.pow(2, zoom - tileCenter.zoom), dx = (c.column * kc - tileCenter.column * kt) * tileSize.x * zoomFactor, dy = (c.row * kc - tileCenter.row * kt) * tileSize.y * zoomFactor; return { x: sizeRadius.x + angleCos * dx - angleSin * dy, y: sizeRadius.y + angleSin * dx + angleCos * dy }; }; map.pointCoordinate = function(tileCenter, p) { var kt = Math.pow(2, zoom - tileCenter.zoom), dx = (p.x - sizeRadius.x) / zoomFactor, dy = (p.y - sizeRadius.y) / zoomFactor; return { column: tileCenter.column * kt + (angleCosi * dx - angleSini * dy) / tileSize.x, row: tileCenter.row * kt + (angleSini * dx + angleCosi * dy) / tileSize.y, zoom: zoom }; }; map.locationPoint = function(l) { var k = Math.pow(2, zoom + zoomFraction - 3) / 45, dx = (l.lon - center.lon) * k * tileSize.x, dy = (lat2y(center.lat) - lat2y(l.lat)) * k * tileSize.y; return { x: sizeRadius.x + angleCos * dx - angleSin * dy, y: sizeRadius.y + angleSin * dx + angleCos * dy }; }; map.pointLocation = function(p) { var k = 45 / Math.pow(2, zoom + zoomFraction - 3), dx = (p.x - sizeRadius.x) * k, dy = (p.y - sizeRadius.y) * k; return { lon: center.lon + (angleCosi * dx - angleSini * dy) / tileSize.x, lat: y2lat(lat2y(center.lat) - (angleSini * dx + angleCosi * dy) / tileSize.y) }; }; function rezoom() { if (zoomRange) { if (zoom < zoomRange[0]) zoom = zoomRange[0]; else if (zoom > zoomRange[1]) zoom = zoomRange[1]; } zoomFraction = zoom - (zoom = Math.round(zoom)); zoomFactor = Math.pow(2, zoomFraction); } function recenter() { if (!centerRange) return; var k = 45 / Math.pow(2, zoom + zoomFraction - 3); // constrain latitude var y = Math.max(Math.abs(angleSin * sizeRadius.x + angleCos * sizeRadius.y), Math.abs(angleSini * sizeRadius.x + angleCosi * sizeRadius.y)), lat0 = y2lat(ymin - y * k / tileSize.y), lat1 = y2lat(ymax + y * k / tileSize.y); center.lat = Math.max(lat0, Math.min(lat1, center.lat)); // constrain longitude var x = Math.max(Math.abs(angleSin * sizeRadius.y + angleCos * sizeRadius.x), Math.abs(angleSini * sizeRadius.y + angleCosi * sizeRadius.x)), lon0 = centerRange[0].lon - x * k / tileSize.x, lon1 = centerRange[1].lon + x * k / tileSize.x; center.lon = Math.max(lon0, Math.min(lon1, center.lon)); } // a place to capture mouse events if no tiles exist var rect = po.svg("rect"); rect.setAttribute("visibility", "hidden"); rect.setAttribute("pointer-events", "all"); map.container = function(x) { if (!arguments.length) return container; container = x; container.setAttribute("class", "map"); container.appendChild(rect); return map.resize(); // infer size }; map.focusableParent = function() { for (var p = container; p; p = p.parentNode) { if (p.tabIndex >= 0) return p; } return window; }; map.mouse = function(e) { var point = (container.ownerSVGElement || container).createSVGPoint(); if ((bug44083 < 0) && (window.scrollX || window.scrollY)) { var svg = document.body.appendChild(po.svg("svg")); svg.style.position = "absolute"; svg.style.top = svg.style.left = "0px"; var ctm = svg.getScreenCTM(); bug44083 = !(ctm.f || ctm.e); document.body.removeChild(svg); } if (bug44083) { point.x = e.pageX; point.y = e.pageY; } else { point.x = e.clientX; point.y = e.clientY; } return point.matrixTransform(container.getScreenCTM().inverse()); }; map.size = function(x) { if (!arguments.length) return sizeActual; size = x; return map.resize(); // size tiles }; map.resize = function() { if (!size) { rect.setAttribute("width", "100%"); rect.setAttribute("height", "100%"); b = rect.getBBox(); sizeActual = {x: b.width, y: b.height}; resizer.add(map); } else { sizeActual = size; resizer.remove(map); } rect.setAttribute("width", sizeActual.x); rect.setAttribute("height", sizeActual.y); sizeRadius = {x: sizeActual.x / 2, y: sizeActual.y / 2}; recenter(); map.dispatch({type: "resize"}); return map; }; map.tileSize = function(x) { if (!arguments.length) return tileSize; tileSize = x; map.dispatch({type: "move"}); return map; }; map.center = function(x) { if (!arguments.length) return center; center = x; recenter(); map.dispatch({type: "move"}); return map; }; map.panBy = function(x) { var k = 45 / Math.pow(2, zoom + zoomFraction - 3), dx = x.x * k, dy = x.y * k; return map.center({ lon: center.lon + (angleSini * dy - angleCosi * dx) / tileSize.x, lat: y2lat(lat2y(center.lat) + (angleSini * dx + angleCosi * dy) / tileSize.y) }); }; map.centerRange = function(x) { if (!arguments.length) return centerRange; centerRange = x; if (centerRange) { ymin = centerRange[0].lat > -90 ? lat2y(centerRange[0].lat) : -Infinity; ymax = centerRange[0].lat < 90 ? lat2y(centerRange[1].lat) : Infinity; } else { ymin = -Infinity; ymax = Infinity; } recenter(); map.dispatch({type: "move"}); return map; }; map.zoom = function(x) { if (!arguments.length) return zoom + zoomFraction; zoom = x; rezoom(); return map.center(center); }; map.zoomBy = function(z, x0, l) { if (arguments.length < 2) return map.zoom(zoom + zoomFraction + z); // compute the location of x0 if (arguments.length < 3) l = map.pointLocation(x0); // update the zoom level zoom = zoom + zoomFraction + z; rezoom(); // compute the new point of the location var x1 = map.locationPoint(l); return map.panBy({x: x0.x - x1.x, y: x0.y - x1.y}); }; map.zoomRange = function(x) { if (!arguments.length) return zoomRange; zoomRange = x; return map.zoom(zoom + zoomFraction); }; map.extent = function(x) { if (!arguments.length) return [ map.pointLocation({x: 0, y: sizeActual.y}), map.pointLocation({x: sizeActual.x, y: 0}) ]; // compute the extent in points, scale factor, and center var bl = map.locationPoint(x[0]), tr = map.locationPoint(x[1]), k = Math.max((tr.x - bl.x) / sizeActual.x, (bl.y - tr.y) / sizeActual.y), l = map.pointLocation({x: (bl.x + tr.x) / 2, y: (bl.y + tr.y) / 2}); // update the zoom level zoom = zoom + zoomFraction - Math.log(k) / Math.LN2; rezoom(); // set the new center return map.center(l); }; map.angle = function(x) { if (!arguments.length) return angle; angle = x; angleCos = Math.cos(angle); angleSin = Math.sin(angle); angleCosi = Math.cos(-angle); angleSini = Math.sin(-angle); recenter(); map.dispatch({type: "move"}); return map; }; map.add = function(x) { x.map(map); return map; }; map.remove = function(x) { x.map(null); return map; }; map.dispatch = po.dispatch(map); return map; }; function resizer(e) { for (var i = 0; i < resizer.maps.length; i++) { resizer.maps[i].resize(); } } resizer.maps = []; resizer.add = function(map) { for (var i = 0; i < resizer.maps.length; i++) { if (resizer.maps[i] == map) return; } resizer.maps.push(map); }; resizer.remove = function(map) { for (var i = 0; i < resizer.maps.length; i++) { if (resizer.maps[i] == map) { resizer.maps.splice(i, 1); return; } } }; // Note: assumes single window (no frames, iframes, etc.)! if (window.addEventListener) { window.addEventListener("resize", resizer, false); window.addEventListener("load", resizer, false); } // See http://wiki.openstreetmap.org/wiki/Mercator function y2lat(y) { return 360 / Math.PI * Math.atan(Math.exp(y * Math.PI / 180)) - 90; } function lat2y(lat) { return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)); } po.map.locationCoordinate = function(l) { var k = 1 / 360; return { column: (l.lon + 180) * k, row: (180 - lat2y(l.lat)) * k, zoom: 0 }; }; po.map.coordinateLocation = function(c) { var k = 45 / Math.pow(2, c.zoom - 3); return { lon: k * c.column - 180, lat: y2lat(180 - k * c.row) }; }; // https://bugs.webkit.org/show_bug.cgi?id=44083 var bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; po.layer = function(load, unload) { var layer = {}, cache = layer.cache = po.cache(load, unload).size(512), tile = true, visible = true, zoom, id, map, container = po.svg("g"), transform, levelZoom, levels = {}; container.setAttribute("class", "layer"); for (var i = -4; i <= -1; i++) levels[i] = container.appendChild(po.svg("g")); for (var i = 2; i >= 1; i--) levels[i] = container.appendChild(po.svg("g")); levels[0] = container.appendChild(po.svg("g")); function zoomIn(z) { var end = levels[0].nextSibling; for (; levelZoom < z; levelZoom++) { // -4, -3, -2, -1, +2, +1, =0 // current order // -3, -2, -1, +2, +1, =0, -4 // insertBefore(-4, end) // -3, -2, -1, +1, =0, -4, +2 // insertBefore(+2, end) // -3, -2, -1, =0, -4, +2, +1 // insertBefore(+1, end) // -4, -3, -2, -1, +2, +1, =0 // relabel container.insertBefore(levels[-4], end); container.insertBefore(levels[2], end); container.insertBefore(levels[1], end); var t = levels[-4]; for (var dz = -4; dz < 2;) levels[dz] = levels[++dz]; levels[dz] = t; } } function zoomOut(z) { var end = levels[0].nextSibling; for (; levelZoom > z; levelZoom--) { // -4, -3, -2, -1, +2, +1, =0 // current order // -4, -3, -2, +2, +1, =0, -1 // insertBefore(-1, end) // +2, -4, -3, -2, +1, =0, -1 // insertBefore(+2, -4) // -4, -3, -2, -1, +2, +1, =0 // relabel container.insertBefore(levels[-1], end); container.insertBefore(levels[2], levels[-4]); var t = levels[2]; for (var dz = 2; dz > -4;) levels[dz] = levels[--dz]; levels[dz] = t; } } function move() { var map = layer.map(), // in case the layer is removed mapZoom = map.zoom(), mapZoomFraction = mapZoom - (mapZoom = Math.round(mapZoom)), mapSize = map.size(), mapAngle = map.angle(), tileSize = map.tileSize(), tileCenter = map.locationCoordinate(map.center()); // set the layer zoom levels if (levelZoom != mapZoom) { if (levelZoom < mapZoom) zoomIn(mapZoom); else if (levelZoom > mapZoom) zoomOut(mapZoom); else levelZoom = mapZoom; for (var z = -4; z <= 2; z++) { var l = levels[z]; l.setAttribute("class", "zoom" + (z < 0 ? "" : "+") + z + " zoom" + (mapZoom + z)); l.setAttribute("transform", "scale(" + Math.pow(2, -z) + ")"); } } // set the layer transform container.setAttribute("transform", "translate(" + (mapSize.x / 2) + "," + (mapSize.y / 2) + ")" + (mapAngle ? "rotate(" + mapAngle / Math.PI * 180 + ")" : "") + (mapZoomFraction ? "scale(" + Math.pow(2, mapZoomFraction) + ")" : "") + (transform ? transform.zoomFraction(mapZoomFraction) : "")); // get the coordinates of the four corners var c0 = map.pointCoordinate(tileCenter, zero), c1 = map.pointCoordinate(tileCenter, {x: mapSize.x, y: 0}), c2 = map.pointCoordinate(tileCenter, mapSize), c3 = map.pointCoordinate(tileCenter, {x: 0, y: mapSize.y}); // round to pixel boundary to avoid anti-aliasing artifacts if (!mapZoomFraction && !mapAngle && !transform) { tileCenter.column = (Math.round(tileSize.x * tileCenter.column) + (mapSize.x & 1) / 2) / tileSize.x; tileCenter.row = (Math.round(tileSize.y * tileCenter.row) + (mapSize.y & 1) / 2) / tileSize.y; } // layer-specific coordinate transform if (transform) { c0 = transform.unapply(c0); c1 = transform.unapply(c1); c2 = transform.unapply(c2); c3 = transform.unapply(c3); tileCenter = transform.unapply(tileCenter); } // layer-specific zoom transform var tileLevel = zoom ? zoom(c0.zoom) - c0.zoom : 0; if (tileLevel) { var k = Math.pow(2, tileLevel); c0.column *= k; c0.row *= k; c1.column *= k; c1.row *= k; c2.column *= k; c2.row *= k; c3.column *= k; c3.row *= k; c0.zoom = c1.zoom = c2.zoom = c3.zoom += tileLevel; } // tile-specific projection function projection(c) { var zoom = c.zoom, max = zoom < 0 ? 1 : 1 << zoom, column = c.column % max, row = c.row; if (column < 0) column += max; return { locationPoint: function(l) { var c = po.map.locationCoordinate(l), k = Math.pow(2, zoom - c.zoom); return { x: tileSize.x * (k * c.column - column), y: tileSize.y * (k * c.row - row) }; } }; } // record which tiles are visible var oldLocks = cache.locks(), newLocks = {}; // reset the proxy counts for (var key in oldLocks) { oldLocks[key].proxyCount = 0; } // load the tiles! if (visible && tileLevel > -5 && tileLevel < 3) { var ymax = c0.zoom < 0 ? 1 : 1 << c0.zoom; if (tile) { scanTriangle(c0, c1, c2, 0, ymax, scanLine); scanTriangle(c2, c3, c0, 0, ymax, scanLine); } else { var x = Math.floor((c0.column + c2.column) / 2), y = Math.max(0, Math.min(ymax - 1, Math.floor((c1.row + c3.row) / 2))), z = Math.min(4, c0.zoom); x = x >> z << z; y = y >> z << z; scanLine(x, x + 1, y); } } // scan-line conversion function scanLine(x0, x1, y) { var z = c0.zoom, z0 = 2 - tileLevel, z1 = 4 + tileLevel; for (var x = x0; x < x1; x++) { var t = cache.load({column: x, row: y, zoom: z}, projection); if (!t.ready && !(t.key in newLocks)) { t.proxyRefs = {}; var c, full, proxy; // downsample high-resolution tiles for (var dz = 1; dz <= z0; dz++) { full = true; for (var dy = 0, k = 1 << dz; dy <= k; dy++) { for (var dx = 0; dx <= k; dx++) { proxy = cache.peek(c = { column: (x << dz) + dx, row: (y << dz) + dy, zoom: z + dz }); if (proxy && proxy.ready) { newLocks[proxy.key] = cache.load(c); proxy.proxyCount++; t.proxyRefs[proxy.key] = proxy; } else { full = false; } } } if (full) break; } // upsample low-resolution tiles if (!full) { for (var dz = 1; dz <= z1; dz++) { proxy = cache.peek(c = { column: x >> dz, row: y >> dz, zoom: z - dz }); if (proxy && proxy.ready) { newLocks[proxy.key] = cache.load(c); proxy.proxyCount++; t.proxyRefs[proxy.key] = proxy; break; } } } } newLocks[t.key] = t; } } // position tiles for (var key in newLocks) { var t = newLocks[key], k = Math.pow(2, t.level = t.zoom - tileCenter.zoom); t.element.setAttribute("transform", "translate(" + (t.x = tileSize.x * (t.column - tileCenter.column * k)) + "," + (t.y = tileSize.y * (t.row - tileCenter.row * k)) + ")"); } // remove tiles that are no longer visible for (var key in oldLocks) { if (!(key in newLocks)) { var t = cache.unload(key); t.element.parentNode.removeChild(t.element); delete t.proxyRefs; } } // append tiles that are now visible for (var key in newLocks) { var t = newLocks[key]; if (t.element.parentNode != levels[t.level]) { levels[t.level].appendChild(t.element); if (layer.show) layer.show(t); } } // flush the cache, clearing no-longer-needed tiles cache.flush(); // dispatch the move event layer.dispatch({type: "move"}); } // remove proxy tiles when tiles load function cleanup(e) { if (e.tile.proxyRefs) { for (var proxyKey in e.tile.proxyRefs) { var proxyTile = e.tile.proxyRefs[proxyKey]; if ((--proxyTile.proxyCount <= 0) && cache.unload(proxyKey)) { proxyTile.element.parentNode.removeChild(proxyTile.element); } } delete e.tile.proxyRefs; } } layer.map = function(x) { if (!arguments.length) return map; if (map) { if (map == x) { container.parentNode.appendChild(container); // move to end return layer; } map.off("move", move).off("resize", move); container.parentNode.removeChild(container); } map = x; if (map) { map.container().appendChild(container); if (layer.init) layer.init(container); map.on("move", move).on("resize", move); move(); } return layer; }; layer.container = function() { return container; }; layer.levels = function() { return levels; }; layer.id = function(x) { if (!arguments.length) return id; id = x; container.setAttribute("id", x); return layer; }; layer.visible = function(x) { if (!arguments.length) return visible; if (visible = x) container.removeAttribute("visibility") else container.setAttribute("visibility", "hidden"); if (map) move(); return layer; }; layer.transform = function(x) { if (!arguments.length) return transform; transform = x; if (map) move(); return layer; }; layer.zoom = function(x) { if (!arguments.length) return zoom; zoom = typeof x == "function" || x == null ? x : function() { return x; }; if (map) move(); return layer; }; layer.tile = function(x) { if (!arguments.length) return tile; tile = x; if (map) move(); return layer; }; layer.reload = function() { cache.clear(); if (map) move(); return layer; }; layer.dispatch = po.dispatch(layer); layer.on("load", cleanup); return layer; }; // scan-line conversion function edge(a, b) { if (a.row > b.row) { var t = a; a = b; b = t; } return { x0: a.column, y0: a.row, x1: b.column, y1: b.row, dx: b.column - a.column, dy: b.row - a.row }; } // scan-line conversion function scanSpans(e0, e1, ymin, ymax, scanLine) { var y0 = Math.max(ymin, Math.floor(e1.y0)), y1 = Math.min(ymax, Math.ceil(e1.y1)); // sort edges by x-coordinate if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ? (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) : (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) { var t = e0; e0 = e1; e1 = t; } // scan lines! var m0 = e0.dx / e0.dy, m1 = e1.dx / e1.dy, d0 = e0.dx > 0, // use y + 1 to compute x0 d1 = e1.dx < 0; // use y + 1 to compute x1 for (var y = y0; y < y1; y++) { var x0 = m0 * Math.max(0, Math.min(e0.dy, y + d0 - e0.y0)) + e0.x0, x1 = m1 * Math.max(0, Math.min(e1.dy, y + d1 - e1.y0)) + e1.x0; scanLine(Math.floor(x1), Math.ceil(x0), y); } } // scan-line conversion function scanTriangle(a, b, c, ymin, ymax, scanLine) { var ab = edge(a, b), bc = edge(b, c), ca = edge(c, a); // sort edges by y-length if (ab.dy > bc.dy) { var t = ab; ab = bc; bc = t; } if (ab.dy > ca.dy) { var t = ab; ab = ca; ca = t; } if (bc.dy > ca.dy) { var t = bc; bc = ca; ca = t; } // scan span! scan span! if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine); if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine); } po.image = function() { var image = po.layer(load, unload), url; function load(tile) { var element = tile.element = po.svg("image"), size = image.map().tileSize(); element.setAttribute("preserveAspectRatio", "none"); element.setAttribute("width", size.x); element.setAttribute("height", size.y); if (typeof url == "function") { element.setAttribute("opacity", 0); var tileUrl = url(tile); if (tileUrl != null) { tile.request = po.queue.image(element, tileUrl, function(img) { delete tile.request; tile.ready = true; tile.img = img; element.removeAttribute("opacity"); image.dispatch({type: "load", tile: tile}); }); } else { tile.ready = true; image.dispatch({type: "load", tile: tile}); } } else { tile.ready = true; if (url != null) element.setAttributeNS(po.ns.xlink, "href", url); image.dispatch({type: "load", tile: tile}); } } function unload(tile) { if (tile.request) tile.request.abort(true); } image.url = function(x) { if (!arguments.length) return url; url = typeof x == "string" && /{.}/.test(x) ? po.url(x) : x; return image.reload(); }; return image; }; po.geoJson = function(fetch) { var geoJson = po.layer(load, unload), container = geoJson.container(), url, clip = true, clipId = "org.polymaps." + po.id(), clipHref = "url(#" + clipId + ")", clipPath = container.insertBefore(po.svg("clipPath"), container.firstChild), clipRect = clipPath.appendChild(po.svg("rect")), scale = "auto", zoom = null, features; container.setAttribute("fill-rule", "evenodd"); clipPath.setAttribute("id", clipId); if (!arguments.length) fetch = po.queue.json; function projection(proj) { var l = {lat: 0, lon: 0}; return function(coordinates) { l.lat = coordinates[1]; l.lon = coordinates[0]; var p = proj(l); coordinates.x = p.x; coordinates.y = p.y; return p; }; } function geometry(o, proj) { return o && o.type in types && types[o.type](o, proj); } var types = { Point: function(o, proj) { var p = proj(o.coordinates), c = po.svg("circle"); c.setAttribute("r", 4.5); c.setAttribute("transform", "translate(" + p.x + "," + p.y + ")"); return c; }, MultiPoint: function(o, proj) { var g = po.svg("g"), c = o.coordinates, p, // proj(c[i]) x, // svg:circle i = -1, n = c.length; while (++i < n) { x = g.appendChild(po.svg("circle")); x.setAttribute("r", 4.5); x.setAttribute("transform", "translate(" + (p = proj(c[i])).x + "," + p.y + ")"); } return g; }, LineString: function(o, proj) { var x = po.svg("path"), d = ["M"], c = o.coordinates, p, // proj(c[i]) i = -1, n = c.length; while (++i < n) d.push((p = proj(c[i])).x, ",", p.y, "L"); d.pop(); if (!d.length) return; x.setAttribute("d", d.join("")); return x; }, MultiLineString: function(o, proj) { var x = po.svg("path"), d = [], ci = o.coordinates, cj, // ci[i] i = -1, j, n = ci.length, m; while (++i < n) { cj = ci[i]; j = -1; m = cj.length; d.push("M"); while (++j < m) d.push((p = proj(cj[j])).x, ",", p.y, "L"); d.pop(); } if (!d.length) return; x.setAttribute("d", d.join("")); return x; }, Polygon: function(o, proj) { var x = po.svg("path"), d = [], ci = o.coordinates, cj, // ci[i] i = -1, j, n = ci.length, m; while (++i < n) { cj = ci[i]; j = -1; m = cj.length - 1; d.push("M"); while (++j < m) d.push((p = proj(cj[j])).x, ",", p.y, "L"); d[d.length - 1] = "Z"; } if (!d.length) return; x.setAttribute("d", d.join("")); return x; }, MultiPolygon: function(o, proj) { var x = po.svg("path"), d = [], ci = o.coordinates, cj, // ci[i] ck, // cj[j] i = -1, j, k, n = ci.length, m, l; while (++i < n) { cj = ci[i]; j = -1; m = cj.length; while (++j < m) { ck = cj[j]; k = -1; l = ck.length - 1; d.push("M"); while (++k < l) d.push((p = proj(ck[k])).x, ",", p.y, "L"); d[d.length - 1] = "Z"; } } if (!d.length) return; x.setAttribute("d", d.join("")); return x; }, GeometryCollection: function(o, proj) { var g = po.svg("g"), i = -1, c = o.geometries, n = c.length, x; while (++i < n) { x = geometry(c[i], proj); if (x) g.appendChild(x); } return g; } }; function rescale(o, e, k) { return o.type in rescales && rescales[o.type](o, e, k); } var rescales = { Point: function (o, e, k) { var p = o.coordinates; e.setAttribute("transform", "translate(" + p.x + "," + p.y + ")" + k); }, MultiPoint: function (o, e, k) { var c = o.coordinates, i = -1, n = p.length, x = e.firstChild, p; while (++i < n) { p = c[i]; x.setAttribute("transform", "translate(" + p.x + "," + p.y + ")" + k); x = x.nextSibling; } } }; function load(tile, proj) { var g = tile.element = po.svg("g"); tile.features = []; proj = projection(proj(tile).locationPoint); function update(data) { var updated = []; /* Fetch the next batch of features, if so directed. */ if (data.next) tile.request = fetch(data.next.href, update); /* Convert the GeoJSON to SVG. */ switch (data.type) { case "FeatureCollection": { for (var i = 0; i < data.features.length; i++) { var feature = data.features[i], element = geometry(feature.geometry, proj); if (element) updated.push({element: g.appendChild(element), data: feature}); } break; } case "Feature": { var element = geometry(data.geometry, proj); if (element) updated.push({element: g.appendChild(element), data: data}); break; } default: { var element = geometry(data, proj); if (element) updated.push({element: g.appendChild(element), data: {type: "Feature", geometry: data}}); break; } } tile.ready = true; updated.push.apply(tile.features, updated); geoJson.dispatch({type: "load", tile: tile, features: updated}); } if (url != null) { tile.request = fetch(typeof url == "function" ? url(tile) : url, update); } else { update({type: "FeatureCollection", features: features || []}); } } function unload(tile) { if (tile.request) tile.request.abort(true); } function move() { var zoom = geoJson.map().zoom(), tiles = geoJson.cache.locks(), // visible tiles key, // key in locks tile, // locks[key] features, // tile.features i, // current feature index n, // current feature count, features.length feature, // features[i] k; // scale transform if (scale == "fixed") { for (key in tiles) { if ((tile = tiles[key]).scale != zoom) { k = "scale(" + Math.pow(2, tile.zoom - zoom) + ")"; i = -1; n = (features = tile.features).length; while (++i < n) rescale((feature = features[i]).data.geometry, feature.element, k); tile.scale = zoom; } } } else { for (key in tiles) { i = -1; n = (features = (tile = tiles[key]).features).length; while (++i < n) rescale((feature = features[i]).data.geometry, feature.element, ""); delete tile.scale; } } } geoJson.url = function(x) { if (!arguments.length) return url; url = typeof x == "string" && /{.}/.test(x) ? po.url(x) : x; if (url != null) features = null; if (typeof url == "string") geoJson.tile(false); return geoJson.reload(); }; geoJson.features = function(x) { if (!arguments.length) return features; if (features = x) { url = null; geoJson.tile(false); } return geoJson.reload(); }; geoJson.clip = function(x) { if (!arguments.length) return clip; if (clip) container.removeChild(clipPath); if (clip = x) container.insertBefore(clipPath, container.firstChild); var locks = geoJson.cache.locks(); for (var key in locks) { if (clip) locks[key].element.setAttribute("clip-path", clipHref); else locks[key].element.removeAttribute("clip-path"); } return geoJson; }; var __tile__ = geoJson.tile; geoJson.tile = function(x) { if (arguments.length && !x) geoJson.clip(x); return __tile__.apply(geoJson, arguments); }; var __map__ = geoJson.map; geoJson.map = function(x) { if (x && clipRect) { var size = x.tileSize(); clipRect.setAttribute("width", size.x); clipRect.setAttribute("height", size.y); } return __map__.apply(geoJson, arguments); }; geoJson.scale = function(x) { if (!arguments.length) return scale; if (scale = x) geoJson.on("move", move); else geoJson.off("move", move); if (geoJson.map()) move(); return geoJson; }; geoJson.show = function(tile) { if (clip) tile.element.setAttribute("clip-path", clipHref); else tile.element.removeAttribute("clip-path"); geoJson.dispatch({type: "show", tile: tile, features: tile.features}); return geoJson; }; geoJson.reshow = function() { var locks = geoJson.cache.locks(); for (var key in locks) geoJson.show(locks[key]); return geoJson; }; return geoJson; }; po.dblclick = function() { var dblclick = {}, zoom = "mouse", map, container; function handle(e) { var z = map.zoom(); if (e.shiftKey) z = Math.ceil(z) - z - 1; else z = 1 - z + Math.floor(z); zoom === "mouse" ? map.zoomBy(z, map.mouse(e)) : map.zoomBy(z); } dblclick.zoom = function(x) { if (!arguments.length) return zoom; zoom = x; return dblclick; }; dblclick.map = function(x) { if (!arguments.length) return map; if (map) { container.removeEventListener("dblclick", handle, false); container = null; } if (map = x) { container = map.container(); container.addEventListener("dblclick", handle, false); } return dblclick; }; return dblclick; }; po.drag = function() { var drag = {}, map, container, dragging; function mousedown(e) { if (e.shiftKey) return; dragging = { x: e.clientX, y: e.clientY }; map.focusableParent().focus(); e.preventDefault(); document.body.style.setProperty("cursor", "move", null); } function mousemove(e) { if (!dragging) return; map.panBy({x: e.clientX - dragging.x, y: e.clientY - dragging.y}); dragging.x = e.clientX; dragging.y = e.clientY; } function mouseup(e) { if (!dragging) return; mousemove(e); dragging = null; document.body.style.removeProperty("cursor"); } drag.map = function(x) { if (!arguments.length) return map; if (map) { container.removeEventListener("mousedown", mousedown, false); container = null; } if (map = x) { container = map.container(); container.addEventListener("mousedown", mousedown, false); } return drag; }; window.addEventListener("mousemove", mousemove, false); window.addEventListener("mouseup", mouseup, false); return drag; }; po.wheel = function() { var wheel = {}, timePrev = 0, last = 0, smooth = true, zoom = "mouse", location, map, container; function move(e) { location = null; } // mousewheel events are totally broken! // https://bugs.webkit.org/show_bug.cgi?id=40441 // not only that, but Chrome and Safari differ in re. to acceleration! var inner = document.createElement("div"), outer = document.createElement("div"); outer.style.visibility = "hidden"; outer.style.top = "0px"; outer.style.height = "0px"; outer.style.width = "0px"; outer.style.overflowY = "scroll"; inner.style.height = "2000px"; outer.appendChild(inner); document.body.appendChild(outer); function mousewheel(e) { var delta = e.wheelDelta || -e.detail, point; /* Detect the pixels that would be scrolled by this wheel event. */ if (delta) { if (smooth) { try { outer.scrollTop = 1000; outer.dispatchEvent(e); delta = 1000 - outer.scrollTop; } catch (error) { // Derp! Hope for the best? } delta *= .005; } /* If smooth zooming is disabled, batch events into unit steps. */ else { var timeNow = Date.now(); if (timeNow - timePrev > 200) { delta = delta > 0 ? +1 : -1; timePrev = timeNow; } else { delta = 0; } } } if (delta) { switch (zoom) { case "mouse": { point = map.mouse(e); if (!location) location = map.pointLocation(point); map.off("move", move).zoomBy(delta, point, location).on("move", move); break; } case "location": { map.zoomBy(delta, map.locationPoint(location), location); break; } default: { // center map.zoomBy(delta); break; } } } e.preventDefault(); return false; // for Firefox } wheel.smooth = function(x) { if (!arguments.length) return smooth; smooth = x; return wheel; }; wheel.zoom = function(x, l) { if (!arguments.length) return zoom; zoom = x; location = l; if (map) { if (zoom == "mouse") map.on("move", move); else map.off("move", move); } return wheel; }; wheel.map = function(x) { if (!arguments.length) return map; if (map) { container.removeEventListener("mousemove", move, false); container.removeEventListener("mousewheel", mousewheel, false); container.removeEventListener("MozMousePixelScroll", mousewheel, false); container = null; map.off("move", move); } if (map = x) { if (zoom == "mouse") map.on("move", move); container = map.container(); container.addEventListener("mousemove", move, false); container.addEventListener("mousewheel", mousewheel, false); container.addEventListener("MozMousePixelScroll", mousewheel, false); } return wheel; }; return wheel; }; po.arrow = function() { var arrow = {}, key = {left: 0, right: 0, up: 0, down: 0}, last = 0, repeatTimer, repeatDelay = 250, repeatInterval = 50, speed = 16, map, parent; function keydown(e) { if (e.ctrlKey || e.altKey || e.metaKey) return; var now = Date.now(), dx = 0, dy = 0; switch (e.keyCode) { case 37: { if (!key.left) { last = now; key.left = 1; if (!key.right) dx = speed; } break; } case 39: { if (!key.right) { last = now; key.right = 1; if (!key.left) dx = -speed; } break; } case 38: { if (!key.up) { last = now; key.up = 1; if (!key.down) dy = speed; } break; } case 40: { if (!key.down) { last = now; key.down = 1; if (!key.up) dy = -speed; } break; } default: return; } if (dx || dy) map.panBy({x: dx, y: dy}); if (!repeatTimer && (key.left | key.right | key.up | key.down)) { repeatTimer = setInterval(repeat, repeatInterval); } e.preventDefault(); } function keyup(e) { last = Date.now(); switch (e.keyCode) { case 37: key.left = 0; break; case 39: key.right = 0; break; case 38: key.up = 0; break; case 40: key.down = 0; break; default: return; } if (repeatTimer && !(key.left | key.right | key.up | key.down)) { repeatTimer = clearInterval(repeatTimer); } e.preventDefault(); } function keypress(e) { switch (e.charCode) { case 45: case 95: map.zoom(Math.ceil(map.zoom()) - 1); break; // - _ case 43: case 61: map.zoom(Math.floor(map.zoom()) + 1); break; // = + default: return; } e.preventDefault(); } function repeat() { if (!map) return; if (Date.now() < last + repeatDelay) return; var dx = (key.left - key.right) * speed, dy = (key.up - key.down) * speed; if (dx || dy) map.panBy({x: dx, y: dy}); } arrow.map = function(x) { if (!arguments.length) return map; if (map) { parent.removeEventListener("keypress", keypress, false); parent.removeEventListener("keydown", keydown, false); parent.removeEventListener("keyup", keyup, false); parent = null; } if (map = x) { parent = map.focusableParent(); parent.addEventListener("keypress", keypress, false); parent.addEventListener("keydown", keydown, false); parent.addEventListener("keyup", keyup, false); } return arrow; }; arrow.speed = function(x) { if (!arguments.length) return speed; speed = x; return arrow; }; return arrow; }; po.hash = function() { var hash = {}, s0, // cached location.hash lat = 90 - 1e-8, // allowable latitude range map; var parser = function(map, s) { var args = s.split("/").map(Number); if (args.length < 3 || args.some(isNaN)) return true; // replace bogus hash else { var size = map.size(); map.zoomBy(args[0] - map.zoom(), {x: size.x / 2, y: size.y / 2}, {lat: Math.min(lat, Math.max(-lat, args[1])), lon: args[2]}); } }; var formatter = function(map) { var center = map.center(), zoom = map.zoom(), precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); return "#" + zoom.toFixed(2) + "/" + center.lat.toFixed(precision) + "/" + center.lon.toFixed(precision); }; function move() { var s1 = formatter(map); if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map! } function hashchange() { if (location.hash === s0) return; // ignore spurious hashchange events if (parser(map, (s0 = location.hash).substring(1))) move(); // replace bogus hash } hash.map = function(x) { if (!arguments.length) return map; if (map) { map.off("move", move); window.removeEventListener("hashchange", hashchange, false); } if (map = x) { map.on("move", move); window.addEventListener("hashchange", hashchange, false); location.hash ? hashchange() : move(); } return hash; }; hash.parser = function(x) { if (!arguments.length) return parser; parser = x; return hash; }; hash.formatter = function(x) { if (!arguments.length) return formatter; formatter = x; return hash; }; return hash; }; po.touch = function() { var touch = {}, map, container, rotate = false, last = 0, zoom, angle, locations = {}; // touch identifier -> location window.addEventListener("touchmove", touchmove, false); function touchstart(e) { var i = -1, n = e.touches.length, t = Date.now(); // doubletap detection if ((n == 1) && (t - last < 300)) { var z = map.zoom(); map.zoomBy(1 - z + Math.floor(z), map.mouse(e.touches[0])); e.preventDefault(); } last = t; // store original zoom & touch locations zoom = map.zoom(); angle = map.angle(); while (++i < n) { t = e.touches[i]; locations[t.identifier] = map.pointLocation(map.mouse(t)); } } function touchmove(e) { switch (e.touches.length) { case 1: { // single-touch pan var t0 = e.touches[0]; map.zoomBy(0, map.mouse(t0), locations[t0.identifier]); e.preventDefault(); break; } case 2: { // double-touch pan + zoom + rotate var t0 = e.touches[0], t1 = e.touches[1], p0 = map.mouse(t0), p1 = map.mouse(t1), p2 = {x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2}, // center point c0 = po.map.locationCoordinate(locations[t0.identifier]), c1 = po.map.locationCoordinate(locations[t1.identifier]), c2 = {row: (c0.row + c1.row) / 2, column: (c0.column + c1.column) / 2, zoom: 0}, l2 = po.map.coordinateLocation(c2); // center location map.zoomBy(Math.log(e.scale) / Math.LN2 + zoom - map.zoom(), p2, l2); if (rotate) map.angle(e.rotation / 180 * Math.PI + angle); e.preventDefault(); break; } } } touch.rotate = function(x) { if (!arguments.length) return rotate; rotate = x; return touch; }; touch.map = function(x) { if (!arguments.length) return map; if (map) { container.removeEventListener("touchstart", touchstart, false); container = null; } if (map = x) { container = map.container(); container.addEventListener("touchstart", touchstart, false); } return touch; }; return touch; }; // Default map controls. po.interact = function() { var interact = {}, drag = po.drag(), wheel = po.wheel(), dblclick = po.dblclick(), touch = po.touch(), arrow = po.arrow(); interact.map = function(x) { drag.map(x); wheel.map(x); dblclick.map(x); touch.map(x); arrow.map(x); return interact; }; return interact; }; po.compass = function() { var compass = {}, g = po.svg("g"), ticks = {}, r = 30, speed = 16, last = 0, repeatDelay = 250, repeatInterval = 50, position = "top-left", // top-left, top-right, bottom-left, bottom-right zoomStyle = "small", // none, small, big zoomContainer, panStyle = "small", // none, small panTimer, panDirection, panContainer, drag, dragRect = po.svg("rect"), map, container, window; g.setAttribute("class", "compass"); dragRect.setAttribute("class", "back fore"); dragRect.setAttribute("pointer-events", "none"); dragRect.setAttribute("display", "none"); function panStart(e) { g.setAttribute("class", "compass active"); if (!panTimer) panTimer = setInterval(panRepeat, repeatInterval); if (panDirection) map.panBy(panDirection); last = Date.now(); return cancel(e); } function panRepeat() { if (panDirection && (Date.now() > last + repeatDelay)) { map.panBy(panDirection); } } function mousedown(e) { if (e.shiftKey) { drag = {x0: map.mouse(e)}; map.focusableParent().focus(); return cancel(e); } } function mousemove(e) { if (!drag) return; drag.x1 = map.mouse(e); dragRect.setAttribute("x", Math.min(drag.x0.x, drag.x1.x)); dragRect.setAttribute("y", Math.min(drag.x0.y, drag.x1.y)); dragRect.setAttribute("width", Math.abs(drag.x0.x - drag.x1.x)); dragRect.setAttribute("height", Math.abs(drag.x0.y - drag.x1.y)); dragRect.removeAttribute("display"); } function mouseup(e) { g.setAttribute("class", "compass"); if (drag) { if (drag.x1) { map.extent([ map.pointLocation({ x: Math.min(drag.x0.x, drag.x1.x), y: Math.max(drag.x0.y, drag.x1.y) }), map.pointLocation({ x: Math.max(drag.x0.x, drag.x1.x), y: Math.min(drag.x0.y, drag.x1.y) }) ]); dragRect.setAttribute("display", "none"); } drag = null; } if (panTimer) { clearInterval(panTimer); panTimer = 0; } } function panBy(x) { return function() { x ? this.setAttribute("class", "active") : this.removeAttribute("class"); panDirection = x; }; } function zoomBy(x) { return function(e) { g.setAttribute("class", "compass active"); var z = map.zoom(); map.zoom(x < 0 ? Math.ceil(z) - 1 : Math.floor(z) + 1); return cancel(e); }; } function zoomTo(x) { return function(e) { map.zoom(x); return cancel(e); }; } function zoomOver() { this.setAttribute("class", "active"); } function zoomOut() { this.removeAttribute("class"); } function cancel(e) { e.stopPropagation(); e.preventDefault(); return false; } function pan(by) { var x = Math.SQRT1_2 * r, y = r * .7, z = r * .2, g = po.svg("g"), dir = g.appendChild(po.svg("path")), chv = g.appendChild(po.svg("path")); dir.setAttribute("class", "direction"); dir.setAttribute("pointer-events", "all"); dir.setAttribute("d", "M0,0L" + x + "," + x + "A" + r + "," + r + " 0 0,1 " + -x + "," + x + "Z"); chv.setAttribute("class", "chevron"); chv.setAttribute("d", "M" + z + "," + (y - z) + "L0," + y + " " + -z + "," + (y - z)); chv.setAttribute("pointer-events", "none"); g.addEventListener("mousedown", panStart, false); g.addEventListener("mouseover", panBy(by), false); g.addEventListener("mouseout", panBy(null), false); g.addEventListener("dblclick", cancel, false); return g; } function zoom(by) { var x = r * .4, y = x / 2, g = po.svg("g"), back = g.appendChild(po.svg("path")), dire = g.appendChild(po.svg("path")), chev = g.appendChild(po.svg("path")), fore = g.appendChild(po.svg("path")); back.setAttribute("class", "back"); back.setAttribute("d", "M" + -x + ",0V" + -x + "A" + x + "," + x + " 0 1,1 " + x + "," + -x + "V0Z"); dire.setAttribute("class", "direction"); dire.setAttribute("d", back.getAttribute("d")); chev.setAttribute("class", "chevron"); chev.setAttribute("d", "M" + -y + "," + -x + "H" + y + (by > 0 ? "M0," + (-x - y) + "V" + -y : "")); fore.setAttribute("class", "fore"); fore.setAttribute("fill", "none"); fore.setAttribute("d", back.getAttribute("d")); g.addEventListener("mousedown", zoomBy(by), false); g.addEventListener("mouseover", zoomOver, false); g.addEventListener("mouseout", zoomOut, false); g.addEventListener("dblclick", cancel, false); return g; } function tick(i) { var x = r * .2, y = r * .4, g = po.svg("g"), back = g.appendChild(po.svg("rect")), chev = g.appendChild(po.svg("path")); back.setAttribute("pointer-events", "all"); back.setAttribute("fill", "none"); back.setAttribute("x", -y); back.setAttribute("y", -.75 * y); back.setAttribute("width", 2 * y); back.setAttribute("height", 1.5 * y); chev.setAttribute("class", "chevron"); chev.setAttribute("d", "M" + -x + ",0H" + x); g.addEventListener("mousedown", zoomTo(i), false); g.addEventListener("dblclick", cancel, false); return g; } function move() { var x = r + 6, y = x, size = map.size(); switch (position) { case "top-left": break; case "top-right": x = size.x - x; break; case "bottom-left": y = size.y - y; break; case "bottom-right": x = size.x - x; y = size.y - y; break; } g.setAttribute("transform", "translate(" + x + "," + y + ")"); dragRect.setAttribute("transform", "translate(" + -x + "," + -y + ")"); for (var i in ticks) { i == map.zoom() ? ticks[i].setAttribute("class", "active") : ticks[i].removeAttribute("class"); } } function draw() { while (g.lastChild) g.removeChild(g.lastChild); g.appendChild(dragRect); if (panStyle != "none") { panContainer = g.appendChild(po.svg("g")); panContainer.setAttribute("class", "pan"); var back = panContainer.appendChild(po.svg("circle")); back.setAttribute("class", "back"); back.setAttribute("r", r); var s = panContainer.appendChild(pan({x: 0, y: -speed})); s.setAttribute("transform", "rotate(0)"); var w = panContainer.appendChild(pan({x: speed, y: 0})); w.setAttribute("transform", "rotate(90)"); var n = panContainer.appendChild(pan({x: 0, y: speed})); n.setAttribute("transform", "rotate(180)"); var e = panContainer.appendChild(pan({x: -speed, y: 0})); e.setAttribute("transform", "rotate(270)"); var fore = panContainer.appendChild(po.svg("circle")); fore.setAttribute("fill", "none"); fore.setAttribute("class", "fore"); fore.setAttribute("r", r); } else { panContainer = null; } if (zoomStyle != "none") { zoomContainer = g.appendChild(po.svg("g")); zoomContainer.setAttribute("class", "zoom"); var j = -.5; if (zoomStyle == "big") { ticks = {}; for (var i = map.zoomRange()[0], j = 0; i <= map.zoomRange()[1]; i++, j++) { (ticks[i] = zoomContainer.appendChild(tick(i))) .setAttribute("transform", "translate(0," + (-(j + .75) * r * .4) + ")"); } } var p = panStyle == "none" ? .4 : 2; zoomContainer.setAttribute("transform", "translate(0," + r * (/^top-/.test(position) ? (p + (j + .5) * .4) : -p) + ")"); zoomContainer.appendChild(zoom(+1)).setAttribute("transform", "translate(0," + (-(j + .5) * r * .4) + ")"); zoomContainer.appendChild(zoom(-1)).setAttribute("transform", "scale(-1)"); } else { zoomContainer = null; } move(); } compass.radius = function(x) { if (!arguments.length) return r; r = x; if (map) draw(); return compass; }; compass.speed = function(x) { if (!arguments.length) return r; speed = x; return compass; }; compass.position = function(x) { if (!arguments.length) return position; position = x; if (map) draw(); return compass; }; compass.pan = function(x) { if (!arguments.length) return panStyle; panStyle = x; if (map) draw(); return compass; }; compass.zoom = function(x) { if (!arguments.length) return zoomStyle; zoomStyle = x; if (map) draw(); return compass; }; compass.map = function(x) { if (!arguments.length) return map; if (map) { container.removeEventListener("mousedown", mousedown, false); container.removeChild(g); container = null; window.removeEventListener("mousemove", mousemove, false); window.removeEventListener("mouseup", mouseup, false); window = null; map.off("move", move).off("resize", move); } if (map = x) { container = map.container(); container.appendChild(g); container.addEventListener("mousedown", mousedown, false); window = container.ownerDocument.defaultView; window.addEventListener("mousemove", mousemove, false); window.addEventListener("mouseup", mouseup, false); map.on("move", move).on("resize", move); draw(); } return compass; }; return compass; }; po.grid = function() { var grid = {}, map, g = po.svg("g"); g.setAttribute("class", "grid"); function move(e) { var p, line = g.firstChild, size = map.size(), nw = map.pointLocation(zero), se = map.pointLocation(size), step = Math.pow(2, 4 - Math.round(map.zoom())); // Round to step. nw.lat = Math.floor(nw.lat / step) * step; nw.lon = Math.ceil(nw.lon / step) * step; // Longitude ticks. for (var x; (x = map.locationPoint(nw).x) <= size.x; nw.lon += step) { if (!line) line = g.appendChild(po.svg("line")); line.setAttribute("x1", x); line.setAttribute("x2", x); line.setAttribute("y1", 0); line.setAttribute("y2", size.y); line = line.nextSibling; } // Latitude ticks. for (var y; (y = map.locationPoint(nw).y) <= size.y; nw.lat -= step) { if (!line) line = g.appendChild(po.svg("line")); line.setAttribute("y1", y); line.setAttribute("y2", y); line.setAttribute("x1", 0); line.setAttribute("x2", size.x); line = line.nextSibling; } // Remove extra ticks. while (line) { var next = line.nextSibling; g.removeChild(line); line = next; } } grid.map = function(x) { if (!arguments.length) return map; if (map) { g.parentNode.removeChild(g); map.off("move", move).off("resize", move); } if (map = x) { map.on("move", move).on("resize", move); map.container().appendChild(g); map.dispatch({type: "move"}); } return grid; }; return grid; }; po.stylist = function() { var attrs = [], styles = [], title; function stylist(e) { var ne = e.features.length, na = attrs.length, ns = styles.length, f, // feature d, // data o, // element x, // attr or style or title descriptor v, // attr or style or title value i, j; for (i = 0; i < ne; ++i) { if (!(o = (f = e.features[i]).element)) continue; d = f.data; for (j = 0; j < na; ++j) { v = (x = attrs[j]).value; if (typeof v === "function") v = v.call(null, d); v == null ? (x.name.local ? o.removeAttributeNS(x.name.space, x.name.local) : o.removeAttribute(x.name)) : (x.name.local ? o.setAttributeNS(x.name.space, x.name.local, v) : o.setAttribute(x.name, v)); } for (j = 0; j < ns; ++j) { v = (x = styles[j]).value; if (typeof v === "function") v = v.call(null, d); v == null ? o.style.removeProperty(x.name) : o.style.setProperty(x.name, v, x.priority); } if (v = title) { if (typeof v === "function") v = v.call(null, d); while (o.lastChild) o.removeChild(o.lastChild); if (v != null) o.appendChild(po.svg("title")).appendChild(document.createTextNode(v)); } } } stylist.attr = function(n, v) { attrs.push({name: ns(n), value: v}); return stylist; }; stylist.style = function(n, v, p) { styles.push({name: n, value: v, priority: arguments.length < 3 ? null : p}); return stylist; }; stylist.title = function(v) { title = v; return stylist; }; return stylist; }; })(org.polymaps);