diff --git a/css/50_misc.css b/css/50_misc.css
index 531b09a0d..be90c5e73 100644
--- a/css/50_misc.css
+++ b/css/50_misc.css
@@ -71,7 +71,7 @@ path.stroke.tag-barrier {
/* bridges */
path.casing.tag-bridge {
stroke-opacity: 0.6;
- stroke: #000;
+ stroke: #000 !important;
stroke-linecap: butt;
stroke-dasharray: none;
}
diff --git a/css/60_photos.css b/css/60_photos.css
index fd4109720..cc853900c 100644
--- a/css/60_photos.css
+++ b/css/60_photos.css
@@ -19,16 +19,52 @@
border-radius: 0;
padding: 5px;
position: absolute;
- right: 0;
- top: 0;
- z-index: 48;
+ right: 5px;
+ top: 5px;
+ z-index: 50;
}
+#photoviewer button.resize-handle-xy {
+ border-radius: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 49;
+ cursor: nesw-resize;
+ height: 25px;
+ width: 25px;
+}
+
+#photoviewer button.resize-handle-x {
+ border-radius: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 48;
+ cursor: ew-resize;
+ height: auto;
+ width: 6px;
+}
+
+#photoviewer button.resize-handle-y {
+ border-radius: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 48;
+ cursor: ns-resize;
+ height: 6px;
+ width: 100%;
+}
+
+
.photo-wrapper,
.photo-wrapper img {
width: 100%;
height: 100%;
overflow: hidden;
+ object-fit: cover;
}
.photo-wrapper .photo-attribution {
@@ -186,7 +222,7 @@
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
- pading: 0 5px;
+ padding: 0 5px;
}
.ms-wrapper .photo-attribution .image-view-link {
text-align: left;
@@ -262,6 +298,8 @@ label.streetside-hires {
}
.osc-image-wrap {
+ width: 100%;
+ height: 100%;
transform-origin:0 0;
-ms-transform-origin:0 0;
-webkit-transform-origin:0 0;
diff --git a/css/65_data.css b/css/65_data.css
index fbad3ba9e..07146df57 100644
--- a/css/65_data.css
+++ b/css/65_data.css
@@ -127,12 +127,16 @@
border-radius: 20px;
}
.comment-main {
- padding: 10px;
+ padding: 10px 10px 10px 0;
flex: 1 1 100%;
flex-flow: column nowrap;
overflow: hidden;
overflow-wrap: break-word;
}
+[dir='rtl'] .comment-main {
+ padding: 10px 0 10px 10px;
+}
+
.comment-metadata {
flex-flow: row nowrap;
justify-content: space-between;
@@ -154,14 +158,18 @@
border-left: none;
}
-#new-comment-input {
+.note-save {
+ padding: 10px;
+}
+
+.note-save #new-comment-input {
width: 100%;
height: 100px;
max-height: 300px;
min-height: 100px;
}
-.note-save-section {
+.note-save .detail-section {
margin: 10px 0;
}
diff --git a/css/80_app.css b/css/80_app.css
index 7475ffd59..c6c8c0d91 100644
--- a/css/80_app.css
+++ b/css/80_app.css
@@ -607,8 +607,13 @@ button.save.has-count .count::before {
margin-right: 5px;
}
[dir='rtl'] .icon.pre-text {
- margin-left: 5px;
- margin-right: 0;
+ margin-left: 5px;
+ margin-right: 0;
+}
+
+.icon.pre-text.user-icon {
+ margin-left: 5px;
+ margin-right: 5px;
}
.icon.light {
@@ -1320,6 +1325,12 @@ a.hide-toggle {
border: 1px solid #ccc;
}
+/* no scrollbars */
+.inspector-hover div {
+ overflow-x: hidden;
+ overflow-y: hidden;
+}
+
/* hide and remove from layout */
.inspector-hidden,
.inspector-hover label input[type="checkbox"],
@@ -3841,7 +3852,6 @@ svg.mouseclick use.right {
}
-
/* Save Mode
------------------------------------------------------- */
.mode-save a.user-info {
@@ -3870,6 +3880,7 @@ svg.mouseclick use.right {
color: #fff;
}
+.note-save .field-warning,
.mode-save .field-warning {
background: #ffb;
border: 1px solid #ccc;
@@ -3877,6 +3888,7 @@ svg.mouseclick use.right {
padding: 10px;
}
+.note-save .field-warning:empty,
.mode-save .field-warning:empty {
display: none;
}
diff --git a/data/core.yaml b/data/core.yaml
index 9827276fd..125a58538 100644
--- a/data/core.yaml
+++ b/data/core.yaml
@@ -280,8 +280,8 @@ en:
localized_translation_language: Choose language
localized_translation_name: Name
zoom_in_edit: Zoom in to edit
- login: login
- logout: logout
+ login: Log In
+ logout: Log Out
loading_auth: "Connecting to OpenStreetMap..."
report_a_bug: Report a bug
help_translate: Help translate
@@ -638,6 +638,9 @@ en:
new: New Note
newDescription: "Describe the issue."
newNote: Add Note
+ login: You must log in to change or comment on this note.
+ upload_explanation: "Your comments will be publicly visible to all OpenStreetMap users."
+ upload_explanation_with_user: "Your comments as {user} will be publicly visible to all OpenStreetMap users."
help:
title: Help
key: H
diff --git a/dist/locales/en.json b/dist/locales/en.json
index 0ecfd4606..a7599f7fb 100644
--- a/dist/locales/en.json
+++ b/dist/locales/en.json
@@ -358,8 +358,8 @@
"localized_translation_name": "Name"
},
"zoom_in_edit": "Zoom in to edit",
- "login": "login",
- "logout": "logout",
+ "login": "Log In",
+ "logout": "Log Out",
"loading_auth": "Connecting to OpenStreetMap...",
"report_a_bug": "Report a bug",
"help_translate": "Help translate",
@@ -772,7 +772,10 @@
"report": "Report",
"new": "New Note",
"newDescription": "Describe the issue.",
- "newNote": "Add Note"
+ "newNote": "Add Note",
+ "login": "You must log in to change or comment on this note.",
+ "upload_explanation": "Your comments will be publicly visible to all OpenStreetMap users.",
+ "upload_explanation_with_user": "Your comments as {user} will be publicly visible to all OpenStreetMap users."
},
"help": {
"title": "Help",
diff --git a/modules/lib/d3.geo.tile.js b/modules/lib/d3.geo.tile.js
deleted file mode 100644
index 3b0f718ef..000000000
--- a/modules/lib/d3.geo.tile.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import { range as d3_range } from 'd3-array';
-
-
-export function d3geoTile() {
- 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(val) {
- return Math.min(_scaleExtent[1], Math.max(_scaleExtent[0], val));
- }
-
- 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
- ];
-
- 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)
- );
-
- 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];
-
- 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;
- };
-
- return tile;
-}
diff --git a/modules/lib/index.js b/modules/lib/index.js
index b6ddad54a..bee98416c 100644
--- a/modules/lib/index.js
+++ b/modules/lib/index.js
@@ -1,3 +1,2 @@
export { d3combobox } from './d3.combobox';
-export { d3geoTile } from './d3.geo.tile';
export { d3keybinding } from './d3.keybinding';
diff --git a/modules/renderer/tile_layer.js b/modules/renderer/tile_layer.js
index 475147e43..d5c59e9b9 100644
--- a/modules/renderer/tile_layer.js
+++ b/modules/renderer/tile_layer.js
@@ -1,15 +1,14 @@
import { select as d3_select } from 'd3-selection';
import { t } from '../util/locale';
-import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
import { geoScaleToZoom, geoVecLength } from '../geo';
-import { utilPrefixCSSProperty } from '../util';
+import { utilPrefixCSSProperty, utilTile } from '../util';
export function rendererTileLayer(context) {
var tileSize = 256;
var transformProp = utilPrefixCSSProperty('Transform');
- var geotile = d3_geoTile();
+ var geotile = utilTile();
var _projection;
var _cache = {};
diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js
index 80340d790..cdf04aa3b 100644
--- a/modules/services/mapillary.js
+++ b/modules/services/mapillary.js
@@ -18,10 +18,12 @@ import {
import rbush from 'rbush';
-import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
import { geoExtent } from '../geo';
import { svgDefs } from '../svg';
-import { utilQsString, utilRebind } from '../util';
+import { utilDetect } from '../util/detect';
+import { utilQsString, utilRebind, utilTile } from '../util';
+
+var geoTile = utilTile();
var apibase = 'https://a.mapillary.com/v3/';
var viewercss = 'mapillary-js/mapillary.min.css';
@@ -63,33 +65,18 @@ function maxPageAtZoom(z) {
if (z > 18) return 80;
}
-function getTiles(projection) {
- 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);
- var origin = [
- s / 2 - projection.translate()[0],
- s / 2 - projection.translate()[1]
- ];
- return d3_geoTile()
- .scaleExtent([tileZoom, tileZoom])
- .scale(s)
- .size(projection.clipExtent()[1])
- .translate(projection.translate())()
- .map(function(tile) {
- var x = tile[0] * ts - origin[0];
- var y = tile[1] * ts - origin[1];
-
- return {
- id: tile.toString(),
- xyz: tile,
- extent: geoExtent(
- projection.invert([x, y + ts]),
- projection.invert([x + ts, y])
- )
- };
- });
+function localeTimestamp(s) {
+ if (!s) return null;
+ var detected = utilDetect();
+ var options = {
+ day: 'numeric', month: 'short', year: 'numeric',
+ hour: 'numeric', minute: 'numeric', second: 'numeric',
+ timeZone: 'UTC'
+ };
+ var d = new Date(s);
+ if (isNaN(d.getTime())) return null;
+ return d.toLocaleString(detected.locale, options);
}
@@ -97,15 +84,11 @@ function loadTiles(which, url, projection) {
var s = projection.scale() * 2 * Math.PI;
var currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
- var tiles = getTiles(projection).filter(function(t) {
- return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
- });
+ var dimension = projection.clipExtent()[1];
+ var tiles = geoTile.getTiles(projection, dimension, tileZoom, 0);
+ tiles = geoTile.filterNullIsland(tiles);
- _filter(which.inflight, function(v, k) {
- var wanted = _find(tiles, function(tile) { return k === (tile.id + ',0'); });
- if (!wanted) delete which.inflight[k];
- return !wanted;
- }).map(abortRequest);
+ geoTile.removeInflightRequests(which, tiles, abortRequest, ',0');
tiles.forEach(function(tile) {
loadNextTilePage(which, currZoom, url, tile);
@@ -410,6 +393,13 @@ export default {
// load mapillary signs sprite
var defs = context.container().select('defs');
defs.call(svgDefs(context).addSprites, ['mapillary-sprite']);
+
+ // Register viewer resize handler
+ context.ui().on('photoviewerResize', function() {
+ if (_mlyViewer) {
+ _mlyViewer.resize();
+ }
+ });
},
diff --git a/modules/services/openstreetcam.js b/modules/services/openstreetcam.js
index 14ded2d6b..73483dddc 100644
--- a/modules/services/openstreetcam.js
+++ b/modules/services/openstreetcam.js
@@ -22,9 +22,9 @@ import {
import rbush from 'rbush';
-import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
import { geoExtent } from '../geo';
+import { utilTile } from '../util';
import { utilDetect } from '../util/detect';
import {
@@ -33,18 +33,19 @@ import {
utilSetTransform
} from '../util';
+var geoTile = utilTile();
-var apibase = 'https://openstreetcam.org',
- maxResults = 1000,
- tileZoom = 14,
- dispatch = d3_dispatch('loadedImages'),
- imgZoom = d3_zoom()
- .extent([[0, 0], [320, 240]])
- .translateExtent([[0, 0], [320, 240]])
- .scaleExtent([1, 15])
- .on('zoom', zoomPan),
- _oscCache,
- _oscSelectedImage;
+var apibase = 'https://openstreetcam.org';
+var maxResults = 1000;
+var tileZoom = 14;
+var dispatch = d3_dispatch('loadedImages');
+var imgZoom = d3_zoom()
+ .extent([[0, 0], [320, 240]])
+ .translateExtent([[0, 0], [320, 240]])
+ .scaleExtent([1, 15])
+ .on('zoom', zoomPan);
+var _oscCache;
+var _oscSelectedImage;
function abortRequest(i) {
@@ -74,48 +75,15 @@ function maxPageAtZoom(z) {
}
-function getTiles(projection) {
- var s = projection.scale() * 2 * Math.PI,
- z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
- ts = 256 * Math.pow(2, z - tileZoom),
- origin = [
- s / 2 - projection.translate()[0],
- s / 2 - projection.translate()[1]];
-
- return d3_geoTile()
- .scaleExtent([tileZoom, tileZoom])
- .scale(s)
- .size(projection.clipExtent()[1])
- .translate(projection.translate())()
- .map(function(tile) {
- var x = tile[0] * ts - origin[0],
- y = tile[1] * ts - origin[1];
-
- return {
- id: tile.toString(),
- xyz: tile,
- extent: geoExtent(
- projection.invert([x, y + ts]),
- projection.invert([x + ts, y])
- )
- };
- });
-}
-
-
function loadTiles(which, url, projection) {
var s = projection.scale() * 2 * Math.PI,
currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
- var tiles = getTiles(projection).filter(function(t) {
- return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
- });
+ var dimension = projection.clipExtent()[1];
+ var tiles = geoTile.getTiles(projection, dimension, tileZoom, 0);
+ tiles = geoTile.filterNullIsland(tiles);
- _filter(which.inflight, function(v, k) {
- var wanted = _find(tiles, function(tile) { return k === (tile.id + ',0'); });
- if (!wanted) delete which.inflight[k];
- return !wanted;
- }).map(abortRequest);
+ geoTile.removeInflightRequests(which, tiles, abortRequest, ',0');
tiles.forEach(function(tile) {
loadNextTilePage(which, currZoom, url, tile);
@@ -129,12 +97,12 @@ function loadNextTilePage(which, currZoom, url, tile) {
var maxPages = maxPageAtZoom(currZoom);
var nextPage = cache.nextPage[tile.id] || 1;
var params = utilQsString({
- ipp: maxResults,
- page: nextPage,
- // client_id: clientId,
- bbTopLeft: [bbox.maxY, bbox.minX].join(','),
- bbBottomRight: [bbox.minY, bbox.maxX].join(',')
- }, true);
+ ipp: maxResults,
+ page: nextPage,
+ // client_id: clientId,
+ bbTopLeft: [bbox.maxY, bbox.minX].join(','),
+ bbBottomRight: [bbox.minY, bbox.maxX].join(',')
+ }, true);
if (nextPage > maxPages) return;
@@ -367,6 +335,16 @@ export default {
.attr('class', 'osc-image-wrap');
+ // Register viewer resize handler
+ context.ui().on('photoviewerResize', function(dimensions) {
+ imgZoom = d3_zoom()
+ .extent([[0, 0], dimensions])
+ .translateExtent([[0, 0], dimensions])
+ .scaleExtent([1, 15])
+ .on('zoom', zoomPan);
+ });
+
+
function rotate(deg) {
return function() {
if (!_oscSelectedImage) return;
diff --git a/modules/services/osm.js b/modules/services/osm.js
index 7b641715f..079a64c1a 100644
--- a/modules/services/osm.js
+++ b/modules/services/osm.js
@@ -17,7 +17,6 @@ import { xml as d3_xml } from 'd3-request';
import osmAuth from 'osm-auth';
import { JXON } from '../util/jxon';
-import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
import { geoExtent, geoVecAdd } from '../geo';
import {
@@ -31,9 +30,11 @@ import {
import {
utilRebind,
utilIdleWorker,
+ utilTile,
utilQsString
} from '../util';
+var geoTile = utilTile();
var dispatch = d3_dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded', 'loadedNotes');
var urlroot = 'https://www.openstreetmap.org';
@@ -789,44 +790,13 @@ export default {
tilezoom = _tileZoom;
}
- 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);
- var origin = [
- s / 2 - projection.translate()[0],
- s / 2 - projection.translate()[1]
- ];
-
- // what tiles cover the view?
- var tiler = d3_geoTile()
- .scaleExtent([tilezoom, tilezoom])
- .scale(s)
- .size(dimensions)
- .translate(projection.translate());
-
- var tiles = tiler().map(function(tile) {
- var x = tile[0] * ts - origin[0];
- var y = tile[1] * ts - origin[1];
-
- return {
- id: tile.toString(),
- extent: geoExtent(
- projection.invert([x, y + ts]),
- projection.invert([x + ts, y])
- )
- };
- });
+ // get tiles
+ var tiles = geoTile.getTiles(projection, dimensions, tilezoom, 0);
+ tiles = geoTile.filterNullIsland(tiles);
// remove inflight requests that no longer cover the view..
var hadRequests = !_isEmpty(cache.inflight);
- _filter(cache.inflight, function(v, i) {
- var wanted = _find(tiles, function(tile) { return i === tile.id; });
- if (!wanted) {
- delete cache.inflight[i];
- }
- return !wanted;
- }).map(abortRequest);
-
+ geoTile.removeInflightRequests(cache, tiles, abortRequest);
if (hadRequests && !loadingNotes && _isEmpty(cache.inflight)) {
dispatch.call('loaded'); // stop the spinner
}
diff --git a/modules/services/streetside.js b/modules/services/streetside.js
index f13470b6c..52ee2c14f 100644
--- a/modules/services/streetside.js
+++ b/modules/services/streetside.js
@@ -17,7 +17,6 @@ import {
import rbush from 'rbush';
import { t } from '../util/locale';
import { jsonpRequest } from '../util/jsonp_request';
-import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
import {
geoExtent,
@@ -29,10 +28,12 @@ import {
} from '../geo';
import { utilDetect } from '../util/detect';
-import { utilQsString, utilRebind } from '../util';
+import { utilQsString, utilRebind, utilTile } from '../util';
import Q from 'q';
+var geoTile = utilTile();
+
var bubbleApi = 'https://dev.virtualearth.net/mapcontrol/HumanScaleServices/GetBubbles.ashx?';
var streetsideImagesApi = 'https://t.ssl.ak.tiles.virtualearth.net/tiles/';
var bubbleAppKey = 'AuftgJsO0Xs8Ts4M1xZUQJQXJNsvmh3IV8DkNieCiy3tCwCUMq76-WpkrBtNAuEm';
@@ -85,46 +86,6 @@ function localeTimestamp(s) {
return d.toLocaleString(detected.locale, options);
}
-/**
- * getTiles() returns array of d3 geo tiles.
- * 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, 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.
- // See 'Ground Resolution and Map Scale': //https://msdn.microsoft.com/en-us/library/bb259689.aspx.
- // As used here, by subtracting constant 'tileZoom' from z (the level), you end up with a much smaller value for the tile size (in pixels).
- 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);
- var origin = [
- s / 2 - projection.translate()[0],
- s / 2 - projection.translate()[1]
- ];
-
- var tiler = d3_geoTile()
- .scaleExtent([tileZoom, tileZoom])
- .scale(s)
- .size(projection.clipExtent()[1])
- .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 {
- id: tile.toString(),
- xyz: tile,
- extent: geoExtent(
- projection.invert([x, y + ts]),
- projection.invert([x + ts, y])
- )
- };
- });
-}
/**
* loadTiles() wraps the process of generating tiles and then fetching image points for each tile.
@@ -133,10 +94,9 @@ 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, margin).filter(function (t) {
- return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
- });
+ var dimension = projection.clipExtent()[1];
+ var tiles = geoTile.getTiles(projection, dimension, tileZoom, margin);
+ tiles = geoTile.filterNullIsland(tiles);
tiles.forEach(function (tile) {
loadNextTilePage(which, currZoom, url, tile);
@@ -669,6 +629,14 @@ export default {
.attr('src', context.asset(pannellumViewerJS));
+ // Register viewer resize handler
+ context.ui().on('photoviewerResize', function() {
+ if (_pannellumViewer) {
+ _pannellumViewer.resize();
+ }
+ });
+
+
function step(stepBy) {
return function() {
var viewer = d3_select('#photoviewer');
diff --git a/modules/ui/commit.js b/modules/ui/commit.js
index ec79e7baf..8cf633cdf 100644
--- a/modules/ui/commit.js
+++ b/modules/ui/commit.js
@@ -167,7 +167,7 @@ export function uiCommit(context) {
userLink
.append('a')
- .attr('class','user-info')
+ .attr('class', 'user-info')
.text(user.display_name)
.attr('href', osm.userURL(user.display_name))
.attr('tabindex', -1)
diff --git a/modules/ui/init.js b/modules/ui/init.js
index 7e1d134fc..4059f9420 100644
--- a/modules/ui/init.js
+++ b/modules/ui/init.js
@@ -2,6 +2,7 @@ import {
event as d3_event,
select as d3_select
} from 'd3-selection';
+import { dispatch as d3_dispatch } from 'd3-dispatch';
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
@@ -13,6 +14,7 @@ import { modeBrowse } from '../modes';
import { services } from '../services';
import { svgDefs, svgIcon } from '../svg';
import { utilGetDimensions } from '../util/dimensions';
+import { utilRebind } from '../util';
import { uiAccount } from './account';
import { uiAttribution } from './attribution';
@@ -45,6 +47,7 @@ import { uiCmd } from './cmd';
export function uiInit(context) {
var uiInitCounter = 0;
+ var dispatch = d3_dispatch('photoviewerResize');
function render(container) {
@@ -256,7 +259,33 @@ export function uiInit(context) {
.append('div')
.call(svgIcon('#iD-icon-close'));
+ photoviewer
+ .append('button')
+ .attr('class', 'resize-handle-xy')
+ .on(
+ 'mousedown',
+ buildResizeListener(photoviewer, 'photoviewerResize', dispatch, { resizeOnX: true, resizeOnY: true })
+ );
+ photoviewer
+ .append('button')
+ .attr('class', 'resize-handle-x')
+ .on(
+ 'mousedown',
+ buildResizeListener(photoviewer, 'photoviewerResize', dispatch, { resizeOnX: true })
+ );
+
+ photoviewer
+ .append('button')
+ .attr('class', 'resize-handle-y')
+ .on(
+ 'mousedown',
+ buildResizeListener(photoviewer, 'photoviewerResize', dispatch, { resizeOnY: true })
+ );
+
+ var mapDimensions = map.dimensions();
+
+ // bind events
window.onbeforeunload = function() {
return context.save();
};
@@ -265,30 +294,13 @@ export function uiInit(context) {
context.history().unlock();
};
- var mapDimensions = map.dimensions();
-
-
- function onResize() {
- mapDimensions = utilGetDimensions(content, true);
- map.dimensions(mapDimensions);
- }
-
d3_select(window)
.on('resize.editor', onResize);
onResize();
- function pan(d) {
- return function() {
- d3_event.preventDefault();
- context.pan(d, 100);
- };
- }
-
-
- // pan amount
- var pa = 80;
+ var pa = 80; // pan amount
var keybinding = d3_keybinding('main')
.on('⌫', function() { d3_event.preventDefault(); })
.on('←', pan([pa, 0]))
@@ -316,8 +328,8 @@ export function uiInit(context) {
.call(uiShortcuts(context));
}
- var osm = context.connection(),
- auth = uiLoading(context).message(t('loading_auth')).blocking(true);
+ var osm = context.connection();
+ var auth = uiLoading(context).message(t('loading_auth')).blocking(true);
if (osm && auth) {
osm
@@ -336,6 +348,85 @@ export function uiInit(context) {
hash.startWalkthrough = false;
context.container().call(uiIntro(context));
}
+
+
+ function onResize() {
+ mapDimensions = utilGetDimensions(content, true);
+ map.dimensions(mapDimensions);
+
+ // shrink photo viewer if it is too big
+ // (-90 preserves space at top and bottom of map used by menus)
+ var photoDimensions = utilGetDimensions(photoviewer, true);
+ if (photoDimensions[0] > mapDimensions[0] || photoDimensions[1] > (mapDimensions[1] - 90)) {
+ var setPhotoDimensions = [
+ Math.min(photoDimensions[0], mapDimensions[0]),
+ Math.min(photoDimensions[1], mapDimensions[1] - 90),
+ ];
+
+ photoviewer
+ .style('width', setPhotoDimensions[0] + 'px')
+ .style('height', setPhotoDimensions[1] + 'px');
+
+ dispatch.call('photoviewerResize', photoviewer, setPhotoDimensions);
+ }
+ }
+
+
+ function pan(d) {
+ return function() {
+ d3_event.preventDefault();
+ context.pan(d, 100);
+ };
+ }
+
+ function buildResizeListener(target, eventName, dispatch, options) {
+ var resizeOnX = !!options.resizeOnX;
+ var resizeOnY = !!options.resizeOnY;
+ var minHeight = options.minHeight || 240;
+ var minWidth = options.minWidth || 320;
+ var startX;
+ var startY;
+ var startWidth;
+ var startHeight;
+
+ function startResize() {
+ var mapSize = context.map().dimensions();
+
+ if (resizeOnX) {
+ var maxWidth = mapSize[0];
+ var newWidth = clamp((startWidth + d3_event.clientX - startX), minWidth, maxWidth);
+ target.style('width', newWidth + 'px');
+ }
+
+ if (resizeOnY) {
+ var maxHeight = mapSize[1] - 90; // preserve space at top/bottom of map
+ var newHeight = clamp((startHeight + startY - d3_event.clientY), minHeight, maxHeight);
+ target.style('height', newHeight + 'px');
+ }
+
+ dispatch.call(eventName, target, utilGetDimensions(target, true));
+ }
+
+ function clamp(num, min, max) {
+ return Math.max(min, Math.min(num, max));
+ }
+
+ function stopResize() {
+ d3_select(window)
+ .on('.' + eventName, null);
+ }
+
+ return function initResize() {
+ startX = d3_event.clientX;
+ startY = d3_event.clientY;
+ startWidth = target.node().getBoundingClientRect().width;
+ startHeight = target.node().getBoundingClientRect().height;
+
+ d3_select(window)
+ .on('mousemove.' + eventName, startResize, false)
+ .on('mouseup.' + eventName, stopResize, false);
+ };
+ }
}
@@ -370,5 +461,5 @@ export function uiInit(context) {
ui.sidebar = uiSidebar(context);
- return ui;
+ return utilRebind(ui, dispatch, 'on');
}
diff --git a/modules/ui/note_comments.js b/modules/ui/note_comments.js
index 7d35b9935..ccf3d5537 100644
--- a/modules/ui/note_comments.js
+++ b/modules/ui/note_comments.js
@@ -66,7 +66,7 @@ export function uiNoteComments() {
mainEnter
.append('div')
.attr('class', 'comment-text')
- .text(function(d) { return d.text; });
+ .html(function(d) { return d.html; });
comments
.call(replaceAvatars);
@@ -101,6 +101,7 @@ export function uiNoteComments() {
if (!s) return null;
var detected = utilDetect();
var options = { day: 'numeric', month: 'short', year: 'numeric' };
+ s = s.replace(/-/g, '/'); // fix browser-specific Date() issues
var d = new Date(s);
if (isNaN(d.getTime())) return null;
return d.toLocaleDateString(detected.locale, options);
diff --git a/modules/ui/note_editor.js b/modules/ui/note_editor.js
index 865b298e4..1ded94d46 100644
--- a/modules/ui/note_editor.js
+++ b/modules/ui/note_editor.js
@@ -1,5 +1,8 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
-import { select as d3_select } from 'd3-selection';
+import {
+ event as d3_event,
+ select as d3_select
+} from 'd3-selection';
import { t } from '../util/locale';
import { services } from '../services';
@@ -53,29 +56,42 @@ export function uiNoteEditor(context) {
.attr('class', 'body')
.merge(body);
- body.selectAll('.note-editor')
- .data([0])
- .enter()
+ var editor = body.selectAll('.note-editor')
+ .data([0]);
+
+ editor = editor.enter()
.append('div')
.attr('class', 'modal-section note-editor')
+ .merge(editor)
.call(noteHeader.note(_note))
.call(noteComments.note(_note))
- .call(noteSave);
+ .call(noteSaveSection);
- selection.selectAll('.footer')
- .data([0])
- .enter()
+ var footer = selection.selectAll('.footer')
+ .data([0]);
+
+ footer = footer.enter()
.append('div')
.attr('class', 'footer')
+ .merge(footer)
.call(uiViewOnOSM(context).what(_note))
.call(uiNoteReport(context).note(_note));
+
+
+ // rerender the note editor on any auth change
+ var osm = services.osm;
+ if (osm) {
+ osm.on('change.note-save', function() {
+ selection.call(noteEditor);
+ });
+ }
}
- function noteSave(selection) {
+ function noteSaveSection(selection) {
var isSelected = (_note && _note.id === context.selectedNoteID());
- var noteSave = selection.selectAll('.note-save-section')
+ var noteSave = selection.selectAll('.note-save')
.data((isSelected ? [_note] : []), function(d) { return d.status + d.id; });
// exit
@@ -85,7 +101,7 @@ export function uiNoteEditor(context) {
// enter
var noteSaveEnter = noteSave.enter()
.append('div')
- .attr('class', 'note-save-section save-section cf');
+ .attr('class', 'note-save save-section cf');
noteSaveEnter
.append('h4')
@@ -107,6 +123,7 @@ export function uiNoteEditor(context) {
// update
noteSave = noteSaveEnter
.merge(noteSave)
+ .call(userDetails)
.call(noteSaveButtons);
@@ -128,7 +145,100 @@ export function uiNoteEditor(context) {
}
+ function userDetails(selection) {
+ var detailSection = selection.selectAll('.detail-section')
+ .data([0]);
+
+ detailSection = detailSection.enter()
+ .append('div')
+ .attr('class', 'detail-section')
+ .merge(detailSection);
+
+ var osm = services.osm;
+ if (!osm) return;
+
+ // Add warning if user is not logged in
+ var hasAuth = osm.authenticated();
+ var authWarning = detailSection.selectAll('.auth-warning')
+ .data(hasAuth ? [] : [0]);
+
+ authWarning.exit()
+ .transition()
+ .duration(200)
+ .style('opacity', 0)
+ .remove();
+
+ var authEnter = authWarning.enter()
+ .insert('div', '.tag-reference-body')
+ .attr('class', 'field-warning auth-warning')
+ .style('opacity', 0);
+
+ authEnter
+ .call(svgIcon('#iD-icon-alert', 'inline'));
+
+ authEnter
+ .append('span')
+ .text(t('note.login'));
+
+ authEnter
+ .append('a')
+ .attr('target', '_blank')
+ .call(svgIcon('#iD-icon-out-link', 'inline'))
+ .append('span')
+ .text(t('login'))
+ .on('click.note-login', function() {
+ d3_event.preventDefault();
+ osm.authenticate();
+ });
+
+ authEnter
+ .transition()
+ .duration(200)
+ .style('opacity', 1);
+
+
+ var prose = detailSection.selectAll('.note-save-prose')
+ .data(hasAuth ? [0] : []);
+
+ prose.exit()
+ .remove();
+
+ prose = prose.enter()
+ .append('p')
+ .attr('class', 'note-save-prose')
+ .text(t('note.upload_explanation'))
+ .merge(prose);
+
+ osm.userDetails(function(err, user) {
+ if (err) return;
+
+ var userLink = d3_select(document.createElement('div'));
+
+ if (user.image_url) {
+ userLink
+ .append('img')
+ .attr('src', user.image_url)
+ .attr('class', 'icon pre-text user-icon');
+ }
+
+ userLink
+ .append('a')
+ .attr('class', 'user-info')
+ .text(user.display_name)
+ .attr('href', osm.userURL(user.display_name))
+ .attr('tabindex', -1)
+ .attr('target', '_blank');
+
+ prose
+ .html(t('note.upload_explanation_with_user', { user: userLink.html() }));
+ });
+ }
+
+
function noteSaveButtons(selection) {
+ var osm = services.osm;
+ var hasAuth = osm && osm.authenticated();
+
var isSelected = (_note && _note.id === context.selectedNoteID());
var buttonSection = selection.selectAll('.buttons')
.data((isSelected ? [_note] : []), function(d) { return d.status + d.id; });
@@ -168,6 +278,7 @@ export function uiNoteEditor(context) {
.merge(buttonEnter);
buttonSection.select('.status-button') // select and propagate data
+ .attr('disabled', (hasAuth ? null : true))
.text(function(d) {
var action = (d.status === 'open' ? 'close' : 'open');
var andComment = (d.newComment ? '_comment' : '');
@@ -186,7 +297,7 @@ export function uiNoteEditor(context) {
buttonSection.select('.comment-button') // select and propagate data
.attr('disabled', function(d) {
- return (d.status === 'open' && d.newComment) ? null : true;
+ return (hasAuth && d.status === 'open' && d.newComment) ? null : true;
})
.on('click.save', function(d) {
this.blur(); // avoid keeping focus on the button - #4641
diff --git a/modules/util/index.js b/modules/util/index.js
index f64856de8..9673bfef5 100644
--- a/modules/util/index.js
+++ b/modules/util/index.js
@@ -23,5 +23,6 @@ export { utilSessionMutex } from './session_mutex';
export { utilStringQs } from './util';
export { utilSuggestNames } from './suggest_names';
export { utilTagText } from './util';
+export { utilTile } from './tile';
export { utilTriggerEvent } from './trigger_event';
export { utilWrap } from './util';
diff --git a/modules/util/tile.js b/modules/util/tile.js
new file mode 100644
index 000000000..2179525f7
--- /dev/null
+++ b/modules/util/tile.js
@@ -0,0 +1,179 @@
+import _filter from 'lodash-es/filter';
+import _find from 'lodash-es/find';
+import { range as d3_range } from 'd3-array';
+import { geoExtent } from '../geo';
+
+
+export function utilTile() {
+ 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(val) {
+ return Math.min(_scaleExtent[1], Math.max(_scaleExtent[0], val));
+ }
+
+ function nearNullIsland(x, y, z) {
+ if (z >= 7) {
+ var center = Math.pow(2, z - 1);
+ var width = Math.pow(2, z - 6);
+ var min = center - (width / 2);
+ var max = center + (width / 2) - 1;
+ return x >= min && x <= max && y >= min && y <= max;
+ }
+ return false;
+ }
+
+ 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
+ ];
+
+ 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)
+ );
+
+ 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];
+
+ 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;
+ }
+
+
+ /**
+ * getTiles() returns array of d3 geo tiles.
+ * 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.
+ */
+ tile.getTiles = function(projection, dimensions, tilezoom, 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.
+ // See 'Ground Resolution and Map Scale': //https://msdn.microsoft.com/en-us/library/bb259689.aspx.
+ // As used here, by subtracting constant 'tileZoom' from z (the level), you end up with a much smaller value for the tile size (in pixels).
+ 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);
+ var origin = [
+ s / 2 - projection.translate()[0],
+ s / 2 - projection.translate()[1]
+ ];
+
+ var tiler = this
+ .scaleExtent([tilezoom, tilezoom])
+ .scale(s)
+ .size(dimensions)
+ .translate(projection.translate())
+ .margin(margin || 0); // request nearby tiles so we can connect sequences.
+
+ var tiles = tiler()
+ .map(function(tile) {
+ var x = tile[0] * ts - origin[0];
+ var y = tile[1] * ts - origin[1];
+
+ return {
+ id: tile.toString(),
+ xyz: tile,
+ extent: geoExtent(
+ projection.invert([x, y + ts]),
+ projection.invert([x + ts, y])
+ )
+ };
+ });
+
+ return tiles;
+ };
+
+
+ tile.filterNullIsland = function(tiles) {
+ return tiles.filter(function(t) {
+ return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
+ });
+ };
+
+
+ // remove inflight requests that no longer cover the view..
+ tile.removeInflightRequests = function(cache, tiles, callback, modifier) {
+ return _filter(cache.inflight, function(v, i) {
+ var wanted = _find(tiles, function(tile) { return i === tile.id + modifier; });
+ if (!wanted) {
+ delete cache.inflight[i];
+ }
+ return !wanted;
+ }).map(callback); // abort request
+ };
+
+
+ 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;
+ };
+
+
+ return tile;
+}
diff --git a/package.json b/package.json
index 3f2f448fc..6148cea94 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
"json-stringify-pretty-compact": "^1.1.0",
"jsonschema": "^1.1.0",
"mapillary-js": "2.12.1",
- "mapillary_sprite_source": "^1.4.0",
+ "mapillary_sprite_source": "^1.5.0",
"minimist": "^1.2.0",
"mocha": "^5.0.0",
"mocha-phantomjs-core": "^2.1.0",
@@ -75,7 +75,7 @@
"osm-community-index": "0.4.5",
"phantomjs-prebuilt": "~2.1.11",
"request": "^2.85.0",
- "rollup": "~0.60.0",
+ "rollup": "~0.63.2",
"rollup-plugin-commonjs": "^9.0.0",
"rollup-plugin-includepaths": "~0.2.3",
"rollup-plugin-json": "^3.0.0",
diff --git a/test/data/mvttest.pbf b/test/data/mvttest.pbf
new file mode 100644
index 000000000..63858a7a9
Binary files /dev/null and b/test/data/mvttest.pbf differ
diff --git a/test/index.html b/test/index.html
index 602943c8d..b094f4b32 100644
--- a/test/index.html
+++ b/test/index.html
@@ -116,6 +116,7 @@
+
diff --git a/test/spec/svg/mvt.js b/test/spec/svg/mvt.js
new file mode 100644
index 000000000..e5ab522aa
--- /dev/null
+++ b/test/spec/svg/mvt.js
@@ -0,0 +1,97 @@
+describe('iD.svgMvt', function () {
+ var context;
+ var surface;
+ var dispatch = d3.dispatch('change');
+ var projection = iD.geoRawMercator()
+ .translate([6934098.868981334, 4092682.5519805425])
+ .scale(iD.geoZoomToScale(17))
+ .clipExtent([[0, 0], [1000, 1000]]);
+
+
+ var gj = {
+ 'type': 'FeatureCollection',
+ 'features': [
+ {
+ 'type': 'Feature',
+ 'id': 316973311,
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [
+ -74.38928604125977,
+ 40.150275473401365
+ ]
+ },
+ 'properties': {
+ 'abbr': 'N.J.',
+ 'area': 19717.8,
+ 'name': 'New Jersey',
+ 'name_en': 'New Jersey',
+ 'osm_id': 316973311
+ }
+ }
+ ]
+ };
+
+ beforeEach(function () {
+ context = iD.coreContext();
+ d3.select(document.createElement('div'))
+ .attr('id', 'map')
+ .call(context.map().centerZoom([-74.389286, 40.1502754], 17));
+
+ surface = context.surface();
+ });
+
+ it('creates layer-mvt', function () {
+ var render = iD.svgMvt(projection, context, dispatch);
+ surface.call(render);
+
+ var layers = surface.selectAll('g.layer-mvt').nodes();
+ expect(layers.length).to.eql(1);
+ });
+
+ it('draws geojson', function () {
+ var render = iD.svgMvt(projection, context, dispatch).geojson(gj);
+ surface.call(render);
+
+ var path = surface.selectAll('path.mvt');
+ expect(path.nodes().length).to.eql(1);
+ expect(path.attr('d')).to.match(/^M.*z$/);
+ });
+
+ describe('#url', function() {
+ it('handles pbf url', function () {
+ var url = '../../data/mvttest.pbf';
+ var render = iD.svgMvt(projection, context, dispatch).url(url);
+ surface.call(render);
+
+ var path = surface.selectAll('path.mvt');
+ expect(path.nodes().length).to.eql(1);
+ expect(path.attr('d')).to.match(/^M.*z$/);
+ });
+ });
+
+ describe('#showLabels', function() {
+ it('shows labels by default', function () {
+ var render = iD.svgMvt(projection, context, dispatch).geojson(gj);
+ surface.call(render);
+
+ var label = surface.selectAll('text.mvtlabel');
+ expect(label.nodes().length).to.eql(1);
+ expect(label.text()).to.eql('New Jersey');
+
+ var halo = surface.selectAll('text.mvtlabel-halo');
+ expect(halo.nodes().length).to.eql(1);
+ expect(halo.text()).to.eql('New Jersey');
+ });
+
+
+ it('hides labels with showLabels(false)', function () {
+ var render = iD.svgMvt(projection, context, dispatch).geojson(gj).showLabels(false);
+ surface.call(render);
+
+ expect(surface.selectAll('text.mvtlabel').empty()).to.be.ok;
+ expect(surface.selectAll('text.mvtlabel-halo').empty()).to.be.ok;
+ });
+ });
+
+});