updated: pull from remote

This commit is contained in:
Thomas Hervey
2018-06-30 08:06:46 -04:00
107 changed files with 28207 additions and 6616 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ export function setAreaKeys(value) {
export function coreContext() {
var context = {};
context.version = '2.9.0';
context.version = '2.9.2';
// create a special translation that contains the keys in place of the strings
var tkeys = _cloneDeep(dataEn);
+81 -55
View File
@@ -2,66 +2,92 @@ import { range as d3_range } from 'd3-array';
export function d3geoTile() {
var size = [960, 500],
scale = 256,
scaleExtent = [0, 20],
translate = [size[0] / 2, size[1] / 2],
zoomDelta = 0;
var _size = [960, 500];
var _scale = 256;
var _scaleExtent = [0, 20];
var _translate = [_size[0] / 2, _size[1] / 2];
var _zoomDelta = 0;
var _margin = 0;
function bound(_) {
return Math.min(scaleExtent[1], Math.max(scaleExtent[0], _));
}
function bound(val) {
return Math.min(_scaleExtent[1], Math.max(_scaleExtent[0], val));
}
function tile() {
var z = Math.max(Math.log(scale) / Math.LN2 - 8, 0),
z0 = bound(Math.round(z + zoomDelta)),
k = Math.pow(2, z - z0 + 8),
origin = [(translate[0] - scale / 2) / k, (translate[1] - scale / 2) / k],
tiles = [],
cols = d3_range(Math.max(0, Math.floor(-origin[0])), Math.max(0, Math.ceil(size[0] / k - origin[0]))),
rows = d3_range(Math.max(0, Math.floor(-origin[1])), Math.max(0, Math.ceil(size[1] / k - origin[1])));
function tile() {
var z = Math.max(Math.log(_scale) / Math.LN2 - 8, 0);
var z0 = bound(Math.round(z + _zoomDelta));
var k = Math.pow(2, z - z0 + 8);
var origin = [
(_translate[0] - _scale / 2) / k,
(_translate[1] - _scale / 2) / k
];
rows.forEach(function(y) {
cols.forEach(function(x) {
tiles.push([x, y, z0]);
});
});
var cols = d3_range(
Math.max(0, Math.floor(-origin[0]) - _margin),
Math.max(0, Math.ceil(_size[0] / k - origin[0]) + _margin)
);
var rows = d3_range(
Math.max(0, Math.floor(-origin[1]) - _margin),
Math.max(0, Math.ceil(_size[1] / k - origin[1]) + _margin)
);
tiles.translate = origin;
tiles.scale = k;
var tiles = [];
for (var i = 0; i < rows.length; i++) {
var y = rows[i];
for (var j = 0; j < cols.length; j++) {
var x = cols[j];
return tiles;
}
if (i >= _margin && i <= rows.length - _margin &&
j >= _margin && j <= cols.length - _margin) {
tiles.unshift([x, y, z0]); // tiles in view at beginning
} else {
tiles.push([x, y, z0]); // tiles in margin at the end
}
}
}
tiles.translate = origin;
tiles.scale = k;
return tiles;
}
tile.scaleExtent = function(val) {
if (!arguments.length) return _scaleExtent;
_scaleExtent = val;
return tile;
};
tile.size = function(val) {
if (!arguments.length) return _size;
_size = val;
return tile;
};
tile.scale = function(val) {
if (!arguments.length) return _scale;
_scale = val;
return tile;
};
tile.translate = function(val) {
if (!arguments.length) return _translate;
_translate = val;
return tile;
};
tile.zoomDelta = function(val) {
if (!arguments.length) return _zoomDelta;
_zoomDelta = +val;
return tile;
};
// number to extend the rows/columns beyond those covering the viewport
tile.margin = function(val) {
if (!arguments.length) return _margin;
_margin = +val;
return tile;
};
tile.scaleExtent = function(_) {
if (!arguments.length) return scaleExtent;
scaleExtent = _;
return tile;
};
tile.size = function(_) {
if (!arguments.length) return size;
size = _;
return tile;
};
tile.scale = function(_) {
if (!arguments.length) return scale;
scale = _;
return tile;
};
tile.translate = function(_) {
if (!arguments.length) return translate;
translate = _;
return tile;
};
tile.zoomDelta = function(_) {
if (!arguments.length) return zoomDelta;
zoomDelta = +_;
return tile;
};
return tile;
}
+19 -38
View File
@@ -1,18 +1,25 @@
import _extend from 'lodash-es/extend';
import { osmEntity } from './entity';
import { geoExtent } from '../geo';
import { debug } from '../index';
export function osmNote() {
if (!(this instanceof osmNote)) return;
this.initialize(arguments);
return this;
if (!(this instanceof osmNote)) {
return (new osmNote()).initialize(arguments);
} else if (arguments.length) {
this.initialize(arguments);
}
}
osmNote.id = function() {
return osmNote.id.next--;
};
osmNote.id.next = -1;
_extend(osmNote.prototype, {
type: 'note',
@@ -31,20 +38,8 @@ _extend(osmNote.prototype, {
}
}
if (!this.id && this.type) {
this.id = osmEntity.id(this.type);
}
if (!this.hasOwnProperty('visible')) {
this.visible = true;
}
if (debug) {
Object.freeze(this);
Object.freeze(this.tags);
if (this.loc) Object.freeze(this.loc);
if (this.nodes) Object.freeze(this.nodes);
if (this.members) Object.freeze(this.members);
if (!this.id) {
this.id = osmNote.id();
}
return this;
@@ -54,21 +49,7 @@ _extend(osmNote.prototype, {
return new geoExtent(this.loc);
},
geometry: function(graph) {
return graph.transient(this, 'geometry', function() {
return graph.isPoi(this) ? 'point' : 'vertex';
});
},
getID: function() {
return this.id;
},
getType: function() {
return this.type;
},
getComments: function() {
return this.comments;
update: function(attrs) {
return osmNote(this, attrs, {v: 1 + (this.v || 0)});
}
});
});
+31 -23
View File
@@ -26,6 +26,15 @@ export function rendererBackground(context) {
function background(selection) {
// If we are displaying an Esri basemap at high zoom,
// check its tilemap to see how high the zoom can go
if (context.map().zoom() > 18) {
var basemap = baseLayer.source();
if (basemap && /^EsriWorldImagery/.test(basemap.id)) {
var center = context.map().center();
basemap.fetchTilemap(center);
}
}
var baseFilter = '';
if (detected.cssfilters) {
@@ -114,16 +123,17 @@ export function rendererBackground(context) {
background.updateImagery = function() {
if (context.inIntro()) return;
var b = background.baseLayerSource(),
o = _overlayLayers
.filter(function (d) { return !d.source().isLocatorOverlay() && !d.source().isHidden(); })
.map(function (d) { return d.source().id; })
.join(','),
meters = geoOffsetToMeters(b.offset()),
epsilon = 0.01,
x = +meters[0].toFixed(2),
y = +meters[1].toFixed(2),
q = utilStringQs(window.location.hash.substring(1));
var b = background.baseLayerSource();
var o = _overlayLayers
.filter(function (d) { return !d.source().isLocatorOverlay() && !d.source().isHidden(); })
.map(function (d) { return d.source().id; })
.join(',');
var meters = geoOffsetToMeters(b.offset());
var epsilon = 0.01;
var x = +meters[0].toFixed(2);
var y = +meters[1].toFixed(2);
var q = utilStringQs(window.location.hash.substring(1));
var id = b.id;
if (id === 'custom') {
@@ -215,13 +225,12 @@ export function rendererBackground(context) {
if (!osm) return background;
var blacklists = context.connection().imageryBlacklists();
var template = d.template();
var fail = false;
var tested = 0;
var regex;
var template = d.template(),
fail = false,
tested = 0,
regex, i;
for (i = 0; i < blacklists.length; i++) {
for (var i = 0; i < blacklists.length; i++) {
try {
regex = new RegExp(blacklists[i]);
fail = regex.test(template);
@@ -270,7 +279,6 @@ export function rendererBackground(context) {
background.toggleOverlayLayer = function(d) {
var layer;
for (var i = 0; i < _overlayLayers.length; i++) {
layer = _overlayLayers[i];
if (layer.source() === d) {
@@ -350,12 +358,12 @@ export function rendererBackground(context) {
return geoExtent([args[2], args[1]]);
}
var dataImagery = data.imagery || [],
q = utilStringQs(window.location.hash.substring(1)),
requested = q.background || q.layer,
extent = parseMap(q.map),
first,
best;
var dataImagery = data.imagery || [];
var q = utilStringQs(window.location.hash.substring(1));
var requested = q.background || q.layer;
var extent = parseMap(q.map);
var first;
var best;
// Add all the available imagery sources
_backgroundSources = dataImagery.map(function(source) {
+82 -36
View File
@@ -6,8 +6,16 @@ import {
geoMercatorRaw as d3_geoMercatorRaw
} from 'd3-geo';
import { json as d3_json } from 'd3-request';
import { t } from '../util/locale';
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
import {
geoExtent,
geoPolygonIntersectsPolygon,
geoSphericalDistance
} from '../geo';
import { jsonpRequest } from '../util/jsonp_request';
import { utilDetect } from '../util/detect';
@@ -109,19 +117,20 @@ export function rendererBackgroundSource(data) {
var lat = Math.atan(sinh(Math.PI * (1 - 2 * y / zoomSize)));
switch (this.projection) {
case 'EPSG:4326': // todo: alternative codes of WGS 84?
return {
x: lon * 180 / Math.PI,
y: lat * 180 / Math.PI
};
default: // EPSG:3857 and synonyms
var mercCoords = d3_geoMercatorRaw(lon, lat);
return {
x: 20037508.34 / Math.PI * mercCoords[0],
y: 20037508.34 / Math.PI * mercCoords[1]
};
case 'EPSG:4326': // todo: alternative codes of WGS 84?
return {
x: lon * 180 / Math.PI,
y: lat * 180 / Math.PI
};
default: // EPSG:3857 and synonyms
var mercCoords = d3_geoMercatorRaw(lon, lat);
return {
x: 20037508.34 / Math.PI * mercCoords[0],
y: 20037508.34 / Math.PI * mercCoords[1]
};
}
}).bind(this);
var minXmaxY = tileToProjectedCoords(coord[0], coord[1], coord[2]);
var maxXminY = tileToProjectedCoords(coord[0]+1, coord[1]+1, coord[2]);
return template
@@ -205,13 +214,13 @@ rendererBackgroundSource.Bing = function(data, dispatch) {
data.template = 'https://ecn.t{switch:0,1,2,3}.tiles.virtualearth.net/tiles/a{u}.jpeg?g=587&mkt=en-gb&n=z';
var bing = rendererBackgroundSource(data),
key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU', // Same as P2 and JOSM
url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' +
key + '&jsonp={callback}',
cache = {},
inflight = {},
providers = [];
var bing = rendererBackgroundSource(data);
var key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU'; // Same as P2 and JOSM
var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' +
key + '&jsonp={callback}';
var cache = {};
var inflight = {};
var providers = [];
jsonpRequest(url, function(json) {
providers = json.resourceSets[0].resources[0].imageryProviders.map(function(provider) {
@@ -244,10 +253,10 @@ rendererBackgroundSource.Bing = function(data, dispatch) {
bing.getMetadata = function(center, tileCoord, callback) {
var tileId = tileCoord.slice(0, 3).join('/'),
zoom = Math.min(tileCoord[2], 21),
centerPoint = center[1] + ',' + center[0], // lat,lng
url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint +
var tileId = tileCoord.slice(0, 3).join('/');
var zoom = Math.min(tileCoord[2], 21);
var centerPoint = center[1] + ',' + center[0]; // lat,lng
var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint +
'?zl=' + zoom + '&key=' + key + '&jsonp={callback}';
if (inflight[tileId]) return;
@@ -290,25 +299,62 @@ rendererBackgroundSource.Bing = function(data, dispatch) {
rendererBackgroundSource.Esri = function(data) {
// don't request blank tiles, instead overzoom real tiles - #4327
// deprecated technique, but it works (for now)
// in addition to using the tilemap at zoom level 20, overzoom real tiles - #4327 (deprecated technique, but it works)
if (data.template.match(/blankTile/) === null) {
data.template = data.template + '?blankTile=false';
}
var esri = rendererBackgroundSource(data),
cache = {},
inflight = {};
var esri = rendererBackgroundSource(data);
var cache = {};
var inflight = {};
var _prevCenter;
// use a tilemap service to set maximum zoom for esri tiles dynamically
// https://developers.arcgis.com/documentation/tiled-elevation-service/
esri.fetchTilemap = function(center) {
// skip if we have already fetched a tilemap within 5km
if (_prevCenter && geoSphericalDistance(center, _prevCenter) < 5000) return;
_prevCenter = center;
// tiles are available globally to zoom level 19, afterward they may or may not be present
var z = 20;
// first generate a random url using the template
var dummyUrl = esri.url([1,2,3]);
// calculate url z/y/x from the lat/long of the center of the map
var x = (Math.floor((center[0] + 180) / 360 * Math.pow(2, z)));
var y = (Math.floor((1 - Math.log(Math.tan(center[1] * Math.PI / 180) + 1 / Math.cos(center[1] * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, z)));
// fetch an 8x8 grid because responses to leverage cache
var tilemapUrl = dummyUrl.replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+/, 'tilemap') + '/' + z + '/' + y + ' /' + x + '/8/8';
// make the request and introspect the response from the tilemap server
d3_json(tilemapUrl, function (err, tilemap) {
if (err || !tilemap) return;
var hasTiles = true;
for (var i = 0; i < tilemap.data.length; i++) {
// 0 means an individual tile in the grid doesn't exist
if (!tilemap.data[i]) {
hasTiles = false;
break;
}
}
// if any tiles are missing at level 20 we restrict maxZoom to 19
esri.scaleExtent[1] = (hasTiles ? 22 : 19);
});
};
esri.getMetadata = function(center, tileCoord, callback) {
var tileId = tileCoord.slice(0, 3).join('/'),
zoom = Math.min(tileCoord[2], esri.scaleExtent[1]),
centerPoint = center[0] + ',' + center[1], // long, lat (as it should be)
unknown = t('info_panels.background.unknown'),
metadataLayer,
vintage = {},
metadata = {};
var tileId = tileCoord.slice(0, 3).join('/');
var zoom = Math.min(tileCoord[2], esri.scaleExtent[1]);
var centerPoint = center[0] + ',' + center[1]; // long, lat (as it should be)
var unknown = t('info_panels.background.unknown');
var metadataLayer;
var vintage = {};
var metadata = {};
if (inflight[tileId]) return;
+16 -3
View File
@@ -6,6 +6,7 @@ import _values from 'lodash-es/values';
import { set as d3_set } from 'd3-collection';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { interpolate as d3_interpolate } from 'd3-interpolate';
import { scaleLinear as d3_scaleLinear } from 'd3-scale';
import {
event as d3_event,
@@ -455,12 +456,24 @@ export function rendererMap(context) {
difference = extent = undefined;
}
var z = String(~~map.zoom());
var zoom = map.zoom();
var z = String(~~zoom);
if (surface.attr('data-zoom') !== z) {
surface.attr('data-zoom', z)
.classed('low-zoom', z <= 16);
surface.attr('data-zoom', z);
}
// class surface as `lowzoom` around z17-z18.5 (based on latitude)
var lat = map.center()[1];
var lowzoom = d3_scaleLinear()
.domain([-60, 0, 60])
.range([17, 18.5, 17])
.clamp(true);
surface
.classed('low-zoom', zoom <= lowzoom(lat));
if (!difference) {
supersurface.call(context.background());
}
+3 -1
View File
@@ -64,7 +64,7 @@ function getTiles(projection) {
s / 2 - projection.translate()[0],
s / 2 - projection.translate()[1]];
return d3_geoTile()
var tiles = d3_geoTile()
.scaleExtent([tileZoom, tileZoom])
.scale(s)
.size(projection.clipExtent()[1])
@@ -82,6 +82,8 @@ function getTiles(projection) {
)
};
});
return tiles;
}
function getLoc(attrs) {
+143 -14
View File
@@ -8,6 +8,10 @@ import _isEmpty from 'lodash-es/isEmpty';
import _map from 'lodash-es/map';
import _uniq from 'lodash-es/uniq';
import rbush from 'rbush';
var _notesCache = {};
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { xml as d3_xml } from 'd3-request';
@@ -19,13 +23,14 @@ import {
osmEntity,
osmNode,
osmRelation,
osmWay
osmWay,
osmNote
} from '../osm';
import { utilRebind, utilIdleWorker } from '../util';
var dispatch = d3_dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded');
var dispatch = d3_dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded', 'loadedNotes');
var urlroot = 'https://www.openstreetmap.org';
var oauth = osmAuth({
url: urlroot,
@@ -39,12 +44,14 @@ var _blacklists = ['.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*'];
var _tiles = { loaded: {}, inflight: {} };
var _changeset = {};
var _entityCache = {};
var _noteTileCache = { loaded: {}, inflight: {}, rtree: rbush() };
var _connectionID = 1;
var _tileZoom = 16;
var _rateLimitError;
var _userChangesets;
var _userDetails;
var _off;
var _loadingNotes = false;
function authLoading() {
@@ -113,6 +120,28 @@ function getVisible(attrs) {
}
function parseComments(comments) {
var parsedComments = [];
// for each comment
_forEach(comments, function(comment) {
if (comment.nodeName === 'comment') {
var childNodes = comment.childNodes;
var parsedComment = {};
_forEach(childNodes, function(node) {
if (node.nodeName !== '#text') {
var nodeName = node.nodeName;
parsedComment[nodeName] = node.innerHTML;
}
});
if (parsedComment) { parsedComments.push(parsedComment); }
}
});
return parsedComments;
}
var parsers = {
node: function nodeData(obj, uid) {
var attrs = obj.attributes;
@@ -157,6 +186,37 @@ var parsers = {
tags: getTags(obj),
members: getMembers(obj)
});
},
note: function parseNote(obj, uid) {
var attrs = obj.attributes;
var childNodes = obj.childNodes;
var parsedNote = {};
parsedNote.loc = getLoc(attrs);
_forEach(childNodes, function(node) {
if (node.nodeName !== '#text') {
var nodeName = node.nodeName;
// if the element is comments, parse the comments
if (nodeName === 'comments') {
parsedNote[nodeName] = parseComments(node.childNodes);
} else {
parsedNote[nodeName] = node.innerHTML;
}
}
});
parsedNote.id = uid;
parsedNote.type = 'note';
return {
minX: parsedNote.loc[0],
minY: parsedNote.loc[1],
maxX: parsedNote.loc[0],
maxY: parsedNote.loc[1],
data: new osmNote(parsedNote)
};
}
};
@@ -171,8 +231,24 @@ function parse(xml, callback, options) {
function parseChild(child) {
var parser = parsers[child.nodeName];
if (parser) {
var uid = osmEntity.id.fromOSM(child.nodeName, child.attributes.id.value);
if (options.cache && _entityCache[uid]) {
var uid;
var cache = _loadingNotes ? _notesCache : _entityCache;
// if loading notes, get the note id
if (_loadingNotes) {
var childNodes = child.childNodes;
_forEach(childNodes, function(node) {
if (node.nodeName === 'id') {
uid = child.nodeName + node.innerHTML;
}
});
}
// otherwise, get the osmEntity id
else {
uid = osmEntity.id.fromOSM(child.nodeName, child.attributes.id.value);
}
if (options.cache && cache[uid]) {
return null;
}
return parser(child, uid);
@@ -200,6 +276,13 @@ export default {
_tiles = { loaded: {}, inflight: {} };
_changeset = {};
_entityCache = {};
if (_noteTileCache && _noteTileCache.inflight) {
_forEach(_noteTileCache.inflight, abortRequest);
}
_noteTileCache = { loaded: {}, inflight: {}, rtree: rbush() };
_notesCache = {};
return this;
},
@@ -272,7 +355,11 @@ export default {
parse(xml, function (entities) {
if (options.cache) {
for (var i in entities) {
_entityCache[entities[i].id] = true;
if (_loadingNotes) {
_notesCache[entities[i].id] = true;
} else {
_entityCache[entities[i].id] = true;
}
}
}
callback(null, entities);
@@ -573,6 +660,9 @@ export default {
if (_off) return;
var that = this;
var cache = _loadingNotes ? _noteTileCache : _tiles;
var s = projection.scale() * 2 * Math.PI;
var z = Math.max(Math.log(s) / Math.log(2) - 8, 0);
var ts = 256 * Math.pow(2, z - _tileZoom);
@@ -598,36 +688,48 @@ export default {
};
});
_filter(_tiles.inflight, function(v, i) {
_filter(cache.inflight, function(v, i) {
var wanted = _find(tiles, function(tile) {
return i === tile.id;
});
if (!wanted) delete _tiles.inflight[i];
if (!wanted) delete cache.inflight[i];
return !wanted;
}).map(abortRequest);
// check if loading entities, or notes
var path = _loadingNotes ? '/api/0.6/notes?bbox=' : '/api/0.6/map?bbox=';
tiles.forEach(function(tile) {
var id = tile.id;
if (_tiles.loaded[id] || _tiles.inflight[id]) return;
if (cache.loaded[id] || cache.inflight[id]) return;
if (_isEmpty(_tiles.inflight)) {
if (_isEmpty(cache.inflight)) {
dispatch.call('loading');
}
_tiles.inflight[id] = that.loadFromAPI(
'/api/0.6/map?bbox=' + tile.extent.toParam(),
cache.inflight[id] = that.loadFromAPI(
path + tile.extent.toParam(),
function(err, parsed) {
delete _tiles.inflight[id];
delete cache.inflight[id];
if (!err) {
_tiles.loaded[id] = true;
cache.loaded[id] = true;
}
// NOTE: if zoom above min zoom & notes turned on before osm, osm won't render
// TODO: either pick this option, or the next one
if (_loadingNotes) {
cache.rtree.load(parsed);
}
// TODO: figure out how this callback should handle parsed results
if (callback) {
callback(err, _extend({ data: parsed }, tile));
}
if (_isEmpty(_tiles.inflight)) {
if (_isEmpty(cache.inflight)) {
if (_loadingNotes) {
dispatch.call('loadedNotes');
}
dispatch.call('loaded');
}
}
@@ -696,5 +798,32 @@ export default {
}
return oauth.authenticate(done);
},
loadNotes: function(projection, dimensions, callback) {
var that = this;
_loadingNotes = true;
that.loadTiles(projection, dimensions, callback);
},
// TODO: possibly remove rtree & refactor caches
notes: function(projection) {
var viewport = projection.clipExtent();
var min = [viewport[0][0], viewport[1][1]];
var max = [viewport[1][0], viewport[0][1]];
var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
return _noteTileCache.rtree.search(bbox)
.map(function(d) { return d.data; });
},
loadedNotes: function(_) {
if (!arguments.length) return _noteTileCache.loaded;
_noteTileCache.loaded = _;
return this;
},
notesCache: function() {
return _noteTileCache;
}
};
+313 -49
View File
@@ -1,3 +1,4 @@
import _extend from 'lodash-es/extend';
import _flatten from 'lodash-es/flatten';
import _forEach from 'lodash-es/forEach';
import _map from 'lodash-es/map';
@@ -8,6 +9,7 @@ import { range as d3_range } from 'd3-array';
import { timer as d3_timer } from 'd3-timer';
import {
event as d3_event,
select as d3_select,
selectAll as d3_selectAll
} from 'd3-selection';
@@ -20,6 +22,7 @@ import { geoExtent } from '../geo';
import { utilDetect } from '../util/detect';
import { utilQsString, utilRebind } from '../util';
import Q from 'q';
var bubbleApi = 'https://dev.virtualearth.net/mapcontrol/HumanScaleServices/GetBubbles.ashx?';
var streetsideImagesApi = 'https://t.ssl.ak.tiles.virtualearth.net/tiles/';
@@ -29,10 +32,16 @@ var pannellumViewerJS = 'pannellum-streetside/pannellum.js';
var maxResults = 2000;
var tileZoom = 16.5;
var dispatch = d3_dispatch('loadedBubbles', 'viewerChanged');
var minHfov = 10; // zoom in degrees: 20, 10, 5
var maxHfov = 90; // zoom out degrees
var defaultHfov = 45;
var _hires = false;
var _resolution = 512; // higher numbers are slower - 512, 1024, 2048, 4096
var _currScene = 0;
var _ssCache;
var _pannellumViewer;
var _sceneOptions;
var _dataUrlArray = [];
/**
* abortRequest().
@@ -72,7 +81,7 @@ function localeTimestamp(s) {
* Using d3.geo.tiles.js from lib, gets tile extents for each grid tile in a grid created from
* an area around (and including) the current map view extents.
*/
function getTiles(projection) {
function getTiles(projection, margin) {
// s is the current map scale
// z is the 'Level of Detail', or zoom-level, where Level 1 is far from the earth, and Level 23 is close to the ground.
// ts ('tile size') here is the formula for determining the width/height of the map in pixels, but with a modification.
@@ -86,12 +95,15 @@ function getTiles(projection) {
s / 2 - projection.translate()[1]
];
return d3_geoTile()
var tiler = d3_geoTile()
.scaleExtent([tileZoom, tileZoom])
.scale(s)
.size(projection.clipExtent()[1])
.translate(projection.translate())()
.map(function (tile) {
.translate(projection.translate())
.margin(margin || 0); // request nearby tiles so we can connect sequences.
return tiler()
.map(function(tile) {
var x = tile[0] * ts - origin[0];
var y = tile[1] * ts - origin[1];
return {
@@ -108,12 +120,12 @@ function getTiles(projection) {
/**
* loadTiles() wraps the process of generating tiles and then fetching image points for each tile.
*/
function loadTiles(which, url, projection) {
function loadTiles(which, url, projection, margin) {
var s = projection.scale() * 2 * Math.PI;
var currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
// breakup the map view into tiles
var tiles = getTiles(projection).filter(function (t) {
var tiles = getTiles(projection, margin).filter(function (t) {
return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
});
@@ -298,6 +310,182 @@ function searchLimited(psize, limit, projection, rtree) {
}
/**
* getImage()
*/
function getImage(imgInfo) {
var response = Q.defer();
var img = new Image();
img.onload = function() {
var canvas = document.getElementById('canvas' + imgInfo.face);
var ctx = canvas.getContext('2d');
ctx.drawImage(img, imgInfo.x, imgInfo.y);
response.resolve({imgInfo:imgInfo, status: 'ok'});
};
img.onerror = function() {
response.resolve({data: imgInfo, status: 'error'});
};
img.setAttribute('crossorigin', '');
img.src = imgInfo.url;
return response.promise;
}
/**
* loadCanvas()
*/
function loadCanvas(imgInfoGroup) {
var response = Q.defer();
var getImagePromises = imgInfoGroup.map(function(imgInfo) {
return getImage(imgInfo);
});
Q.all(getImagePromises).then(function(data) {
var canvas = document.getElementById('canvas' + data[0].imgInfo.face);
switch (data[0].imgInfo.face) {
case '01':
_dataUrlArray[0] = canvas.toDataURL('image/jpeg', 1.0);
break;
case '02':
_dataUrlArray[1] = canvas.toDataURL('image/jpeg', 1.0);
break;
case '03':
_dataUrlArray[2] = canvas.toDataURL('image/jpeg', 1.0);
break;
case '10':
_dataUrlArray[3] = canvas.toDataURL('image/jpeg', 1.0);
break;
case '11':
_dataUrlArray[4] = canvas.toDataURL('image/jpeg', 1.0);
break;
case '12':
_dataUrlArray[5] = canvas.toDataURL('image/jpeg', 1.0);
break;
}
response.resolve({status:'loadCanvas for face ' + data[0].imgInfo.face + 'ok'});
});
return response.promise;
}
function setupCanvas(selection, reset) {
if (reset) {
selection.selectAll('#divForCanvasWork')
.remove();
}
// Add the Streetside working canvases. These are used for 'stitching', or combining,
// multiple images for each of the six faces, before passing to the Pannellum control as DataUrls
selection.selectAll('#divForCanvasWork')
.data([0])
.enter()
.append('div')
.attr('id', 'divForCanvasWork')
.attr('display', 'none')
.selectAll('canvas')
.data(['canvas01', 'canvas02', 'canvas03', 'canvas10', 'canvas11', 'canvas12'])
.enter()
.append('canvas')
.attr('id', function(d) { return d; })
.attr('width', _resolution)
.attr('height', _resolution);
}
/**
* processFaces()
*/
function processFaces(imgFaceInfoGroups) {
var response = Q.defer();
var loadCanvasPromises = imgFaceInfoGroups.map(function(faceImgGroup) {
return loadCanvas(faceImgGroup);
});
Q.all(loadCanvasPromises).then(function() {
response.resolve({status: 'processFaces done'});
});
return response.promise;
}
function qkToXY(qk) {
var x = 0;
var y = 0;
var scale = 256;
for (var i = qk.length; i > 0; i--) {
var key = qk[i-1];
x += (+(key === '1' || key === '3')) * scale;
y += (+(key === '2' || key === '3')) * scale;
scale *= 2;
}
return [x, y];
}
function getQuadKeys() {
var dim = _resolution / 256;
var quadKeys;
if (dim === 16) {
quadKeys = [
'0000','0001','0010','0011','0100','0101','0110','0111', '1000','1001','1010','1011','1100','1101','1110','1111',
'0002','0003','0012','0013','0102','0103','0112','0113', '1002','1003','1012','1013','1102','1103','1112','1113',
'0020','0021','0030','0031','0120','0121','0130','0131', '1020','1021','1030','1031','1120','1121','1130','1131',
'0022','0023','0032','0033','0122','0123','0132','0133', '1022','1023','1032','1033','1122','1123','1132','1133',
'0200','0201','0210','0211','0300','0301','0310','0311', '1200','1201','1210','1211','1300','1301','1310','1311',
'0202','0203','0212','0213','0302','0303','0312','0313', '1202','1203','1212','1213','1302','1303','1312','1313',
'0220','0221','0230','0231','0320','0321','0330','0331', '1220','1221','1230','1231','1320','1321','1330','1331',
'0222','0223','0232','0233','0322','0323','0332','0333', '1222','1223','1232','1233','1322','1323','1332','1333',
'2000','2001','2010','2011','2100','2101','2110','2111', '3000','3001','3010','3011','3100','3101','3110','3111',
'2002','2003','2012','2013','2102','2103','2112','2113', '3002','3003','3012','3013','3102','3103','3112','3113',
'2020','2021','2030','2031','2120','2121','2130','2131', '3020','3021','3030','3031','3120','3121','3130','3131',
'2022','2023','2032','2033','2122','2123','2132','2133', '3022','3023','3032','3033','3122','3123','3132','3133',
'2200','2201','2210','2211','2300','2301','2310','2311', '3200','3201','3210','3211','3300','3301','3310','3311',
'2202','2203','2212','2213','2302','2303','2312','2313', '3202','3203','3212','3213','3302','3303','3312','3313',
'2220','2221','2230','2231','2320','2321','2330','2331', '3220','3221','3230','3231','3320','3321','3330','3331',
'2222','2223','2232','2233','2322','2323','2332','2333', '3222','3223','3232','3233','3322','3323','3332','3333'
];
} else if (dim === 8) {
quadKeys = [
'000','001','010','011', '100','101','110','111',
'002','003','012','013', '102','103','112','113',
'020','021','030','031', '120','121','130','131',
'022','023','032','033', '122','123','132','133',
'200','201','210','211', '300','301','310','311',
'202','203','212','213', '302','303','312','313',
'220','221','230','231', '320','321','330','331',
'222','223','232','233', '322','323','332','333'
];
} else if (dim === 4) {
quadKeys = [
'00','01', '10','11',
'02','03', '12','13',
'20','21', '30','31',
'22','23', '32','33'
];
} else { // dim === 2
quadKeys = [
'0', '1',
'2', '3'
];
}
return quadKeys;
}
export default {
/**
* init() initialize streetside.
@@ -362,8 +550,11 @@ export default {
/**
* loadBubbles()
*/
loadBubbles: function (projection) {
loadTiles('bubbles', bubbleApi, projection);
loadBubbles: function (projection, margin) {
// by default: request 2 nearby tiles so we can connect sequences.
if (margin === undefined) margin = 2;
loadTiles('bubbles', bubbleApi, projection, margin);
},
@@ -427,6 +618,11 @@ export default {
.append('div')
.attr('class', 'photo-attribution fillD');
// create working canvas for stitching together images
wrap = wrap
.merge(wrapEnter)
.call(setupCanvas, true);
// load streetside pannellum viewer css
d3_select('head').selectAll('#streetside-viewercss')
.data([0])
@@ -470,7 +666,6 @@ export default {
_pannellumViewer
.removeScene(sceneID);
}
}
var wrap = d3_select('#photoviewer')
@@ -513,6 +708,9 @@ export default {
* selectImage().
*/
selectImage: function (d) {
var response = Q.defer();
var that = this;
var viewer = d3_select('#photoviewer');
if (!viewer.empty()) viewer.datum(d);
@@ -520,68 +718,134 @@ export default {
var wrap = d3_select('#photoviewer .ms-wrapper');
var attribution = wrap.selectAll('.photo-attribution').html('');
var year = (new Date()).getFullYear();
if (d) {
if (d.captured_by) {
attribution
.append('a')
.attr('class', 'captured_by')
.attr('target', '_blank')
.attr('href', 'https://www.microsoft.com/en-us/maps/streetside')
.text('©' + year + ' Microsoft');
wrap.selectAll('.pnlm-load-box') // display "loading.."
.style('display', 'block');
attribution
.append('span')
.text('|');
}
if (!d) {
response.resolve({status: 'ok'});
return response.promise;
}
if (d.captured_at) {
attribution
.append('span')
.attr('class', 'captured_at')
.text(localeTimestamp(d.captured_at));
}
// Add hires checkbox
var label = attribution
.append('label')
.attr('class', 'streetside-hires');
label
.append('input')
.attr('type', 'checkbox')
.attr('id', 'streetside-hires-input')
.property('checked', _hires)
.on('click', function() {
d3_event.stopPropagation();
_hires = !_hires;
_resolution = _hires ? 1024 : 512;
wrap.call(setupCanvas, true);
var viewstate = {
yaw: _pannellumViewer.getYaw(),
pitch: _pannellumViewer.getPitch(),
hfov: _pannellumViewer.getHfov()
};
that.selectImage(d)
.then(function(r) {
if (r.status === 'ok') {
_sceneOptions = _extend(_sceneOptions, viewstate);
that.showViewer();
}
});
});
label
.append('span')
.text(t('streetside.hires'));
// Add capture date
if (d.captured_by) {
var yyyy = (new Date()).getFullYear();
attribution
.append('a')
.attr('class', 'image_link')
.attr('class', 'captured_by')
.attr('target', '_blank')
.attr('href', 'https://www.bing.com/maps/privacyreport/streetsideprivacyreport?bubbleid=' + encodeURIComponent(d.key) +
'&focus=photo&lat=' + d.loc[1] + '&lng=' + d.loc[0] + '&z=17')
.text(t('streetside.report'));
.attr('href', 'https://www.microsoft.com/en-us/maps/streetside')
.text('©' + yyyy + ' Microsoft');
attribution
.append('span')
.text('|');
}
if (d.captured_at) {
attribution
.append('span')
.attr('class', 'captured_at')
.text(localeTimestamp(d.captured_at));
}
// Add image link
attribution
.append('a')
.attr('class', 'image_link')
.attr('target', '_blank')
.attr('href', 'https://www.bing.com/maps/privacyreport/streetsideprivacyreport?bubbleid=' + encodeURIComponent(d.key) +
'&focus=photo&lat=' + d.loc[1] + '&lng=' + d.loc[0] + '&z=17')
.text(t('streetside.report'));
var bubbleIdQuadKey = d.key.toString(4);
var paddingNeeded = 16 - bubbleIdQuadKey.length;
for (var i = 0; i < paddingNeeded; i++) {
bubbleIdQuadKey = '0' + bubbleIdQuadKey;
}
var bubbleIdQuadKey = d.key.toString(4);
var paddingNeeded = 16 - bubbleIdQuadKey.length;
for (var i = 0; i < paddingNeeded; i++) {
bubbleIdQuadKey = '0' + bubbleIdQuadKey;
}
var imgUrlPrefix = streetsideImagesApi + 'hs' + bubbleIdQuadKey;
var imgUrlSuffix = '.jpg?g=6338&n=z';
// Order matters here: front=01, right=02, back=03, left=10, up=11, down=12
var imgLocIdxArr = ['01','02','03','10','11','12'];
var imgUrlPrefix = streetsideImagesApi + 'hs' + bubbleIdQuadKey;
var imgUrlSuffix = '.jpg?g=6338&n=z';
// Cubemap face code order matters here: front=01, right=02, back=03, left=10, up=11, down=12
var faceKeys = ['01','02','03','10','11','12'];
// Map images to cube faces
var quadKeys = getQuadKeys();
var faces = faceKeys.map(function(faceKey) {
return quadKeys.map(function(quadKey) {
var xy = qkToXY(quadKey);
return {
face: faceKey,
url: imgUrlPrefix + faceKey + quadKey + imgUrlSuffix,
x: xy[0],
y: xy[1]
};
});
});
processFaces(faces).then(function() {
_sceneOptions = {
showFullscreenCtrl: false,
autoLoad: true,
compass: true,
northOffset: d.ca,
yaw: 0,
minHfov: minHfov,
maxHfov: maxHfov,
hfov: defaultHfov,
type: 'cubemap',
cubeMap: [
imgUrlPrefix + imgLocIdxArr[0] + imgUrlSuffix,
imgUrlPrefix + imgLocIdxArr[1] + imgUrlSuffix,
imgUrlPrefix + imgLocIdxArr[2] + imgUrlSuffix,
imgUrlPrefix + imgLocIdxArr[3] + imgUrlSuffix,
imgUrlPrefix + imgLocIdxArr[4] + imgUrlSuffix,
imgUrlPrefix + imgLocIdxArr[5] + imgUrlSuffix
_dataUrlArray[0],
_dataUrlArray[1],
_dataUrlArray[2],
_dataUrlArray[3],
_dataUrlArray[4],
_dataUrlArray[5]
]
};
}
response.resolve({status: 'ok'});
});
return this;
return response.promise;
},
+1 -1
View File
@@ -264,7 +264,7 @@ export default {
// A few OSM keys expect values to contain uppercase values (see #3377).
// This is not an exhaustive list (e.g. `name` also has uppercase values)
// but these are the fields where taginfo value lookup is most useful.
var re = /network|taxon|genus|species|brand|grape_variety|rating|:output|_hours|_times/;
var re = /network|taxon|genus|species|brand|grape_variety|royal_cypher|booth|rating|:output|_hours|_times/;
var allowUpperCase = (params.key.match(re) !== null);
var f = filterValues(allowUpperCase);
+12 -5
View File
@@ -30,10 +30,10 @@ export function svgNotes(projection, context, dispatch) {
}
function getService() {
if (services.notes && !_notes) {
_notes = services.notes;
_notes.event.on('loadedNotes', throttledRedraw);
} else if (!services.notes && _notes) {
if (services.osm && !_notes) {
_notes = services.osm;
_notes.on('loadedNotes', throttledRedraw);
} else if (!services.osm && _notes) {
_notes = null;
}
@@ -107,6 +107,13 @@ export function svgNotes(projection, context, dispatch) {
var enabled = svgNotes.enabled,
service = getService();
function dimensions() {
return [window.innerWidth, window.innerHeight];
}
function done() {
console.log('placeholder done within svg/notes.upload.done');
}
layer = selection.selectAll('.layer-notes')
.data(service ? [0] : []);
@@ -123,7 +130,7 @@ export function svgNotes(projection, context, dispatch) {
if (service && ~~context.map().zoom() >= minZoom) {
editOn();
update();
service.loadNotes(projection);
service.loadNotes(projection, dimensions(), done);
} else {
editOff();
}
+20 -5
View File
@@ -12,6 +12,7 @@ export function svgStreetside(projection, context, dispatch) {
var layer = d3_select(null);
var _viewerYaw = 0;
var _selectedSequence = null;
var _hoveredBubble = null;
var _streetside;
/**
@@ -105,7 +106,12 @@ export function svgStreetside(projection, context, dispatch) {
service
.selectImage(d)
.showViewer(_viewerYaw);
.then(function(r) {
if (r.status === 'ok'){
service.showViewer(_viewerYaw);
}
});
context.map().centerEase(d.loc);
}
@@ -115,7 +121,8 @@ export function svgStreetside(projection, context, dispatch) {
*/
function mouseover(d) {
var service = getService();
if (service) service.setStyles(d);
_hoveredBubble = d;
if (service) service.setStyles(d, true);
}
/**
@@ -123,7 +130,8 @@ export function svgStreetside(projection, context, dispatch) {
*/
function mouseout() {
var service = getService();
if (service) service.setStyles(null);
_hoveredBubble = null;
if (service) service.setStyles(null, true);
}
/**
@@ -188,7 +196,10 @@ export function svgStreetside(projection, context, dispatch) {
var groups = layer.selectAll('.markers').selectAll('.viewfield-group')
.data(bubbles, function(d) { return d.key; });
.data(bubbles, function(d) {
// force reenter once bubbles are attached to a sequence
return d.key + (d.sequenceKey ? 'v1' : 'v0');
});
// exit
groups.exit()
@@ -241,6 +252,11 @@ export function svgStreetside(projection, context, dispatch) {
.attr('d', viewfieldPath);
if (service) {
service.setStyles(_hoveredBubble, true);
}
function viewfieldPath() {
var d = this.parentNode.__data__;
if (d.pano) {
@@ -287,7 +303,6 @@ export function svgStreetside(projection, context, dispatch) {
if (service && ~~context.map().zoom() >= minZoom) {
editOn();
update();
service.loadBubbles(projection);
} else {
editOff();