From d036af8b293a334ef8cc417794982a99f5984ff5 Mon Sep 17 00:00:00 2001 From: john gravois Date: Thu, 5 Apr 2018 12:04:30 -0700 Subject: [PATCH 1/5] add logic to set maxZoom of esri layers interactively --- modules/renderer/background_source.js | 41 ++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index d608aae07..2d24c16ba 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -290,13 +290,6 @@ 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) - if (data.template.match(/blankTile/) === null) { - data.template = data.template + '?blankTile=false'; - } - var esri = rendererBackgroundSource(data), cache = {}, inflight = {}; @@ -312,6 +305,9 @@ rendererBackgroundSource.Esri = function(data) { if (inflight[tileId]) return; + // instead of calling fetchTilemap when the metadata window is open, we should call it when editing is first activated + fetchTilemap(center, esri); + switch (true) { case (zoom >= 20 && esri.id === 'EsriWorldImageryClarity'): metadataLayer = 4; @@ -409,6 +405,37 @@ rendererBackgroundSource.Esri = function(data) { }); } + // use a tilemap service to set maximum zoom for esri tiles dynamically + function fetchTilemap(center, esri) { + // tiles are available globally to zoom level 19, afterward they are only a possibility + const urlZ = 20; + + // calculate url z/y/x from the lat/long of the center of the map + const urlX = (Math.floor((center[0] + 180) / 360 * Math.pow(2, urlZ))); + const urlY = (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, urlZ))); + + // we fetch an 8x8 grid because they cover a normal extent and responses are cached + const tilemapUrl = tileCoord[3].replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+/, 'tilemap') + `/${urlZ}/${urlY}/${urlX}/8/8`; + + // make the request and introspect the response from the tilemap server + fetch(tilemapUrl) + .then(response => response.json()) + .then(tilemap => { + let tiles = true; + for (i=0;i Date: Thu, 10 May 2018 13:08:12 -0700 Subject: [PATCH 2/5] use d3_json instead of fetch --- modules/renderer/background_source.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 2d24c16ba..0543b2e06 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -6,6 +6,8 @@ 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 { jsonpRequest } from '../util/jsonp_request'; @@ -418,9 +420,9 @@ rendererBackgroundSource.Esri = function(data) { const tilemapUrl = tileCoord[3].replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+/, 'tilemap') + `/${urlZ}/${urlY}/${urlX}/8/8`; // make the request and introspect the response from the tilemap server - fetch(tilemapUrl) - .then(response => response.json()) - .then(tilemap => { + d3_json(tilemapUrl, function (err, tilemap) { + if (err || !tilemap) return; + let tiles = true; for (i=0;i Date: Thu, 14 Jun 2018 13:18:00 -0700 Subject: [PATCH 3/5] use existing method to trigger tilemap --- modules/renderer/background_source.js | 70 ++++++++++++++------------- modules/ui/notice.js | 8 +++ 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 0543b2e06..ceb4bda5a 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -296,6 +296,42 @@ rendererBackgroundSource.Esri = function(data) { cache = {}, inflight = {}; + // use a tilemap service to set maximum zoom for esri tiles dynamically + // https://developers.arcgis.com/documentation/tiled-elevation-service/ + esri.fetchTilemap = function(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 + ' /' + '/8/8'; + + // make the request and introspect the response from the tilemap server + d3_json(tilemapUrl, function (err, tilemap) { + if (err || !tilemap) return; + + var tiles = true; + for (var i=0; i= 20 && esri.id === 'EsriWorldImageryClarity'): metadataLayer = 4; @@ -407,37 +440,6 @@ rendererBackgroundSource.Esri = function(data) { }); } - // use a tilemap service to set maximum zoom for esri tiles dynamically - function fetchTilemap(center, esri) { - // tiles are available globally to zoom level 19, afterward they are only a possibility - const urlZ = 20; - - // calculate url z/y/x from the lat/long of the center of the map - const urlX = (Math.floor((center[0] + 180) / 360 * Math.pow(2, urlZ))); - const urlY = (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, urlZ))); - - // we fetch an 8x8 grid because they cover a normal extent and responses are cached - const tilemapUrl = tileCoord[3].replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+/, 'tilemap') + `/${urlZ}/${urlY}/${urlX}/8/8`; - - // make the request and introspect the response from the tilemap server - d3_json(tilemapUrl, function (err, tilemap) { - if (err || !tilemap) return; - - let tiles = true; - for (i=0;i= context.minEditableZoom(); div.style('display', canEdit ? 'none' : 'block'); + // if an Esri basemap is being displayed and native zoom past 19 is enabled, check the tilemap + if (canEdit) { + var basemap = context.background().baseLayerSource(); + if (/^EsriWorldImagery/.test(basemap.id) && basemap.scaleExtent[1] > 19) { + var center = context.map().center(); + basemap.fetchTilemap(center); + } + } } context.map() From eaa9d8d246173b63c3293183743d53d24a9ee08f Mon Sep 17 00:00:00 2001 From: john gravois Date: Fri, 15 Jun 2018 08:21:53 -0700 Subject: [PATCH 4/5] fix recomposed tilemap url --- modules/renderer/background_source.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index ceb4bda5a..60a870104 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -310,7 +310,7 @@ rendererBackgroundSource.Esri = function(data) { 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 + ' /' + '/8/8'; + 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) { From b6fb3a345df88cdc833f434e26741be67e254bbb Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 28 Jun 2018 23:20:08 -0400 Subject: [PATCH 5/5] Fetch Esri tilemap at high zoom to determine max zoom The tilemap is only fetched if the user is at z > 18 and will not fetch again unless the user has moved more than 1km away from the previous fetch --- modules/renderer/background.js | 54 +++++++++------- modules/renderer/background_source.js | 88 +++++++++++++++------------ modules/ui/notice.js | 8 --- 3 files changed, 80 insertions(+), 70 deletions(-) diff --git a/modules/renderer/background.js b/modules/renderer/background.js index dc2bb3d3e..fa2fe6c46 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -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) { diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 60a870104..074796972 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -9,7 +9,13 @@ import { 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'; @@ -111,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 @@ -207,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) { @@ -246,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; @@ -292,13 +299,18 @@ rendererBackgroundSource.Bing = function(data, dispatch) { rendererBackgroundSource.Esri = function(data) { - 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; @@ -316,30 +328,28 @@ rendererBackgroundSource.Esri = function(data) { d3_json(tilemapUrl, function (err, tilemap) { if (err || !tilemap) return; - var tiles = true; - for (var i=0; i= context.minEditableZoom(); div.style('display', canEdit ? 'none' : 'block'); - // if an Esri basemap is being displayed and native zoom past 19 is enabled, check the tilemap - if (canEdit) { - var basemap = context.background().baseLayerSource(); - if (/^EsriWorldImagery/.test(basemap.id) && basemap.scaleExtent[1] > 19) { - var center = context.map().center(); - basemap.fetchTilemap(center); - } - } } context.map()