Created
December 26, 2011 22:34
-
-
Save bmount/1522226 to your computer and use it in GitHub Desktop.
couple of polymaps tweaks for tms 1.0 (template H line ~205) and couchdb bbox (case "G" same vicinity)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 c.column; // was autowrapping at x=0, drop c for compat | |
case "H": return max - c.row - 1; // see issue #96 github--polymaps, tms compat | |
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); | |
} | |
case "G": { // a bbox compatible with geocouch bbox | |
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 nw.lon.toFixed(pn) | |
+ "," + se.lat.toFixed(pn) | |
+ "," + se.lon.toFixed(pn) | |
+ "," + nw.lat.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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment