Merge pull request #5243 from openstreetmap/gpx-mvt

Vector Tiles pt 2
This commit is contained in:
Bryan Housel
2018-08-25 11:40:09 -04:00
committed by GitHub
49 changed files with 1825 additions and 1356 deletions
+1 -1
View File
@@ -324,7 +324,7 @@ correspondence with entities:
* `iD.svgLayers` - sets up a number of layers that ensure that map elements
appear in an appropriate z-order.
* `iD.svgOsm` - sets up the OSM-specific data layers
* `iD.svgGpx` - draws gpx traces
* `iD.svgData` - draws any other overlaid vector data (gpx, kml, geojson, mvt, pbf)
* `iD.svgDebug` - draws debugging information
### Other UI
+4 -1
View File
@@ -27,7 +27,10 @@ module.exports = function buildSrc() {
input: './modules/id.js',
plugins: [
includePaths( {
paths: ['node_modules/d3/node_modules'] // npm2 or windows
paths: ['node_modules/d3/node_modules'], // npm2 or windows
include: {
'martinez-polygon-clipping': 'node_modules/martinez-polygon-clipping/dist/martinez.umd.js'
}
}),
nodeResolve({
module: true,
-60
View File
@@ -306,63 +306,3 @@ g.turn circle {
stroke: #68f;
}
/* GPX Paths */
.layer-gpx {
pointer-events: none;
}
path.gpx {
stroke: #ff26d4;
stroke-width: 2;
fill: none;
}
text.gpxlabel-halo,
text.gpxlabel {
font-size: 10px;
font-weight: bold;
dominant-baseline: middle;
}
text.gpxlabel {
fill: #ff26d4;
}
text.gpxlabel-halo {
opacity: 0.7;
stroke: #000;
stroke-width: 5px;
stroke-miterlimit: 1;
}
/* MVT Paths */
.layer-mvt {
pointer-events: none;
}
path.mvt {
stroke: #ff26d4;
stroke-width: 2;
fill: none;
}
text.mvtlabel-halo,
text.mvtlabel {
font-size: 10px;
font-weight: bold;
dominant-baseline: middle;
}
text.mvtlabel {
fill: #ff26d4;
}
text.mvtlabel-halo {
opacity: 0.7;
stroke: #000;
stroke-width: 5px;
stroke-miterlimit: 1;
}
+51 -114
View File
@@ -8,6 +8,7 @@
}
.mode-browse .layer-notes .note .note-fill,
.mode-select .layer-notes .note .note-fill,
.mode-select-data .layer-notes .note .note-fill,
.mode-select-note .layer-notes .note .note-fill {
pointer-events: visible;
cursor: pointer; /* Opera */
@@ -41,140 +42,76 @@
.note-header-icon .preset-icon-28 {
top: 18px;
}
.note-header-icon .note-icon-annotation {
position: absolute;
top: 22px;
left: 22px;
margin: auto;
}
.note-header-icon .note-icon-annotation .icon {
width: 15px;
height: 15px;
}
/* OSM Note UI */
.note-header {
background-color: #f6f6f6;
border-radius: 5px;
border: 1px solid #ccc;
display: flex;
flex-flow: row nowrap;
align-items: center;
/* Custom Map Data (geojson, gpx, kml, vector tile) */
.layer-mapdata {
pointer-events: none;
}
.note-header-icon {
background-color: #fff;
padding: 10px;
flex: 0 0 62px;
position: relative;
width: 60px;
height: 60px;
border-right: 1px solid #ccc;
border-radius: 5px 0 0 5px;
.layer-mapdata path.shadow {
pointer-events: stroke;
stroke: #f6634f;
stroke-width: 16;
stroke-opacity: 0;
fill: none;
}
[dir='rtl'] .note-header-icon {
border-right: unset;
border-left: 1px solid #ccc;
border-radius: 0 5px 5px 0;
.layer-mapdata path.MultiPoint.shadow,
.layer-mapdata path.Point.shadow {
pointer-events: fill;
fill: #f6634f;
fill-opacity: 0;
}
.layer-mapdata path.shadow.hover:not(.selected) {
stroke-opacity: 0.4;
}
.layer-mapdata path.shadow.selected {
stroke-opacity: 0.7;
}
.note-header-icon .icon-wrap {
position: absolute;
top: 0px;
.layer-mapdata path.stroke {
stroke: #ff26d4;
stroke-width: 2;
fill: none;
}
.note-header-label {
background-color: #f6f6f6;
padding: 0 15px;
flex: 1 1 100%;
font-size: 14px;
.layer-mapdata path.fill {
stroke-width: 0;
stroke-opacity: 0.3;
stroke: #ff26d4;
fill: #ff26d4;
fill-opacity: 0.3;
fill-rule: evenodd;
}
.layer-mapdata text.label-halo,
.layer-mapdata text.label {
font-size: 10px;
font-weight: bold;
border-radius: 0 5px 5px 0;
dominant-baseline: middle;
}
[dir='rtl'] .note-header-label {
border-radius: 5px 0 0 5px;
.layer-mapdata text.label {
fill: #ff26d4;
}
.layer-mapdata text.label.hover,
.layer-mapdata text.label.selected {
fill: #f6634f;
}
.layer-mapdata text.label-halo {
opacity: 0.7;
stroke: #000;
stroke-width: 5px;
stroke-miterlimit: 1;
}
.note-category {
margin: 20px 0px;
}
.comments-container {
background: #ececec;
padding: 1px 10px;
border-radius: 8px;
margin-top: 20px;
}
.comment {
background-color: #fff;
border-radius: 5px;
border: 1px solid #ccc;
margin: 10px auto;
display: flex;
flex-flow: row nowrap;
}
.comment-avatar {
padding: 10px;
flex: 0 0 62px;
}
.comment-avatar .icon.comment-avatar-icon {
width: 40px;
height: 40px;
object-fit: cover;
border: 1px solid #ccc;
border-radius: 20px;
}
.comment-main {
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;
}
.comment-author {
font-weight: bold;
color: #333;
}
.comment-date {
color: #aaa;
}
.comment-text {
color: #333;
margin-top: 10px;
overflow-y: auto;
max-height: 250px;
}
.comment-text::-webkit-scrollbar {
border-left: none;
}
.note-save {
padding: 10px;
}
.note-save #new-comment-input {
width: 100%;
height: 100px;
max-height: 300px;
min-height: 100px;
}
.note-save .detail-section {
margin: 10px 0;
}
.note-report {
float: right;
}
+5
View File
@@ -7,6 +7,11 @@
stroke-dasharray: none !important;
fill: none !important;
}
.low-zoom.fill-wireframe .layer-mapdata path.stroke,
.fill-wireframe .layer-mapdata path.stroke {
stroke-width: 2 !important;
stroke-opacity: 1 !important;
}
.low-zoom.fill-wireframe path.shadow,
.fill-wireframe path.shadow {
+202 -8
View File
@@ -717,6 +717,7 @@ button.add-note svg.icon {
}
.field-help-title button.close,
.sidebar-component .header button.data-editor-close,
.sidebar-component .header button.note-editor-close,
.entity-editor-pane .header button.preset-close,
.preset-list-pane .header button.preset-choose {
@@ -725,6 +726,7 @@ button.add-note svg.icon {
top: 0;
}
[dir='rtl'] .field-help-title button.close,
[dir='rtl'] .sidebar-component .header button.data-editor-close,
[dir='rtl'] .sidebar-component .header button.note-editor-close,
[dir='rtl'] .entity-editor-pane .header button.preset-close,
[dir='rtl'] .preset-list-pane .header button.preset-choose {
@@ -1204,7 +1206,7 @@ a.hide-toggle {
}
/* preset form basics */
/* Preset Editor */
.preset-editor {
overflow: hidden;
@@ -1392,6 +1394,10 @@ a.hide-toggle {
.inspector-hover .tag-row .form-field.input-wrap-position {
width: 50%;
}
.inspector-hover .tag-row .key-wrap,
.inspector-hover .tag-row .input-wrap-position {
height: 31px;
}
.inspector-hover .tag-row:first-child input.value {
border-top-right-radius: 4px;
@@ -2415,6 +2421,185 @@ input.key-trap {
border: 1px solid rgba(0,0,0,0);
}
/* OSM Note UI */
.note-header {
background-color: #f6f6f6;
border-radius: 5px;
border: 1px solid #ccc;
display: flex;
flex-flow: row nowrap;
align-items: center;
}
.note-header-icon {
background-color: #fff;
padding: 10px;
flex: 0 0 62px;
position: relative;
width: 60px;
height: 60px;
border-right: 1px solid #ccc;
border-radius: 5px 0 0 5px;
}
[dir='rtl'] .note-header-icon {
border-right: unset;
border-left: 1px solid #ccc;
border-radius: 0 5px 5px 0;
}
.note-header-icon .icon-wrap {
position: absolute;
top: 0px;
}
.note-header-label {
background-color: #f6f6f6;
padding: 0 15px;
flex: 1 1 100%;
font-size: 14px;
font-weight: bold;
border-radius: 0 5px 5px 0;
}
[dir='rtl'] .note-header-label {
border-radius: 5px 0 0 5px;
}
.note-category {
margin: 20px 0px;
}
.comments-container {
background: #ececec;
padding: 1px 10px;
border-radius: 8px;
margin-top: 20px;
}
.comment {
background-color: #fff;
border-radius: 5px;
border: 1px solid #ccc;
margin: 10px auto;
display: flex;
flex-flow: row nowrap;
}
.comment-avatar {
padding: 10px;
flex: 0 0 62px;
}
.comment-avatar .icon.comment-avatar-icon {
width: 40px;
height: 40px;
object-fit: cover;
border: 1px solid #ccc;
border-radius: 20px;
}
.comment-main {
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;
}
.comment-author {
font-weight: bold;
color: #333;
}
.comment-date {
color: #aaa;
}
.comment-text {
color: #333;
margin-top: 10px;
overflow-y: auto;
max-height: 250px;
}
.comment-text::-webkit-scrollbar {
border-left: none;
}
.note-save {
padding: 10px;
}
.note-save #new-comment-input {
width: 100%;
height: 100px;
max-height: 300px;
min-height: 100px;
}
.note-save .detail-section {
margin: 10px 0;
}
.note-report {
float: right;
}
/* Map Data Inspector */
.data-header {
background-color: #f6f6f6;
border-radius: 5px;
border: 1px solid #ccc;
display: flex;
flex-flow: row nowrap;
align-items: center;
}
.data-header-icon {
background-color: #fff;
padding: 10px;
flex: 0 0 62px;
position: relative;
width: 60px;
height: 60px;
border-right: 1px solid #ccc;
border-radius: 5px 0 0 5px;
}
[dir='rtl'] .data-header-icon {
border-right: unset;
border-left: 1px solid #ccc;
border-radius: 0 5px 5px 0;
}
.data-header-icon .icon-wrap {
position: absolute;
top: 0px;
}
.data-header-label {
background-color: #f6f6f6;
padding: 0 15px;
flex: 1 1 100%;
font-size: 14px;
font-weight: bold;
border-radius: 0 5px 5px 0;
}
[dir='rtl'] .data-header-label {
border-radius: 5px 0 0 5px;
}
/* tag editor - no buttons */
.data-editor.raw-tag-editor button {
display: none;
}
.data-editor.raw-tag-editor .tag-row .key-wrap,
.data-editor.raw-tag-editor .tag-row .input-wrap-position {
width: 50%;
}
/* Fullscreen button */
div.full-screen {
float: right;
@@ -2566,8 +2751,7 @@ div.full-screen > button:hover {
float: right;
}
[dir='rtl'] .list-item-gpx-browse svg,
[dir='rtl'] .list-item-mvt-browse svg {
[dir='rtl'] .list-item-data-browse svg {
transform: rotateY(180deg);
}
@@ -3876,14 +4060,24 @@ svg.mouseclick use.right {
/* Settings Modals
------------------------------------------------------- */
.settings-custom-background .instructions {
.settings-modal textarea {
height: 70px;
}
.settings-modal .buttons .button.col3 {
float: none; /* undo float left */
}
.settings-custom-background .instructions-template {
margin-bottom: 20px;
}
.settings-custom-background textarea {
height: 60px;
.settings-custom-data .instructions-url {
margin-bottom: 10px;
}
.settings-custom-background .buttons .button.col3 {
float: none; /* undo float left */
.settings-custom-data .field-file,
.settings-custom-data .instructions-template {
margin-bottom: 20px;
}
+14 -10
View File
@@ -460,6 +460,10 @@ en:
notes:
tooltip: Note data from OpenStreetMap
title: OpenStreetMap notes
custom:
tooltip: "Drag and drop a data file onto the page, or click the button to setup"
title: Custom Map Data
zoom: Zoom to data
fill_area: Fill Areas
map_features: Map Features
autohidden: "These features have been automatically hidden because too many would be shown on the screen. You can zoom in to edit them."
@@ -519,6 +523,16 @@ en:
instructions: "Enter a tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme\n {-y} or {ty} for flipped TMS-style Y coordinates\n {u} for quadtile scheme\n {switch:a,b,c} for DNS server multiplexing\n\nExample:\n{example}"
template:
placeholder: Enter a url template
custom_data:
tooltip: Edit custom data layer
header: Custom Map Data Settings
file:
instructions: "Choose a local data file. Supported types are:\n .gpx, .kml, .geojson, .json"
label: "Browse files"
or: "Or"
url:
instructions: "Enter a data file URL or vector tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme"
placeholder: Enter a url
restore:
heading: You have unsaved changes
description: "Do you wish to restore unsaved changes from a previous editing session?"
@@ -610,16 +624,6 @@ en:
out: Zoom out
cannot_zoom: "Cannot zoom out further in current mode."
full_screen: Toggle Full Screen
gpx:
local_layer: "Add a GPX"
drag_drop: "Drag and drop a .gpx, .geojson or .kml file on the page, or click the button to the right to browse"
zoom: "Zoom to layer"
browse: "Browse for a file"
mvt:
local_layer: "Add a MVT"
drag_drop: "Drag and drop a .mvt or .pbf file on the page, or click the button to the right to browse"
zoom: "Zoom to layer"
browse: "Browse for a file"
streetside:
tooltip: "Streetside photos from Microsoft"
title: "Photo Overlay (Bing Streetside)"
+18 -12
View File
@@ -558,6 +558,11 @@
"notes": {
"tooltip": "Note data from OpenStreetMap",
"title": "OpenStreetMap notes"
},
"custom": {
"tooltip": "Drag and drop a data file onto the page, or click the button to setup",
"title": "Custom Map Data",
"zoom": "Zoom to data"
}
},
"fill_area": "Fill Areas",
@@ -638,6 +643,19 @@
"template": {
"placeholder": "Enter a url template"
}
},
"custom_data": {
"tooltip": "Edit custom data layer",
"header": "Custom Map Data Settings",
"file": {
"instructions": "Choose a local data file. Supported types are:\n .gpx, .kml, .geojson, .json",
"label": "Browse files"
},
"or": "Or",
"url": {
"instructions": "Enter a data file URL or vector tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme",
"placeholder": "Enter a url"
}
}
},
"restore": {
@@ -741,18 +759,6 @@
},
"cannot_zoom": "Cannot zoom out further in current mode.",
"full_screen": "Toggle Full Screen",
"gpx": {
"local_layer": "Add a GPX",
"drag_drop": "Drag and drop a .gpx, .geojson or .kml file on the page, or click the button to the right to browse",
"zoom": "Zoom to layer",
"browse": "Browse for a file"
},
"mvt": {
"local_layer": "Add a MVT",
"drag_drop": "Drag and drop a .mvt or .pbf file on the page, or click the button to the right to browse",
"zoom": "Zoom to layer",
"browse": "Browse for a file"
},
"streetside": {
"tooltip": "Streetside photos from Microsoft",
"title": "Photo Overlay (Bing Streetside)",
+25 -23
View File
@@ -6,10 +6,7 @@ import {
} from 'd3-selection';
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
import {
osmEntity,
osmNote
} from '../osm';
import { osmEntity, osmNote } from '../osm';
import { utilRebind } from '../util/rebind';
@@ -110,13 +107,32 @@ export function behaviorHover(context) {
_selection.selectAll('.hover-suppressed')
.classed('hover-suppressed', false);
var entity;
if (datum instanceof osmNote || datum instanceof osmEntity) {
// What are we hovering over?
var entity, selector;
if (datum && datum.__featurehash__) {
entity = datum;
} else {
entity = datum && datum.properties && datum.properties.entity;
selector = '.data' + datum.__featurehash__;
} else if (datum instanceof osmNote) {
entity = datum;
selector = '.note-' + datum.id;
} else if (datum instanceof osmEntity) {
entity = datum;
selector = '.' + entity.id;
if (entity.type === 'relation') {
entity.members.forEach(function(member) { selector += ', .' + member.id; });
}
} else if (datum && datum.properties && (datum.properties.entity instanceof osmEntity)) {
entity = datum.properties.entity;
selector = '.' + entity.id;
if (entity.type === 'relation') {
entity.members.forEach(function(member) { selector += ', .' + member.id; });
}
}
// Update hover state and dispatch event
if (entity && entity.id !== _newId) {
// If drawing a way, don't hover on a node that was just placed. #3974
var mode = context.mode() && context.mode().id;
@@ -125,30 +141,16 @@ export function behaviorHover(context) {
return;
}
var selector = (datum instanceof osmNote) ? 'note-' + entity.id : '.' + entity.id;
if (entity.type === 'relation') {
entity.members.forEach(function(member) {
selector += ', .' + member.id;
});
}
var suppressed = _altDisables && d3_event && d3_event.altKey;
_selection.selectAll(selector)
.classed(suppressed ? 'hover-suppressed' : 'hover', true);
if (datum instanceof osmNote) {
dispatch.call('hover', this, !suppressed && entity);
} else {
dispatch.call('hover', this, !suppressed && entity.id);
}
dispatch.call('hover', this, !suppressed && entity);
} else {
dispatch.call('hover', this, null);
}
}
};
+6 -1
View File
@@ -11,6 +11,7 @@ import { geoVecLength } from '../geo';
import {
modeBrowse,
modeSelect,
modeSelectData,
modeSelectNote
} from '../modes';
@@ -157,13 +158,17 @@ export function behaviorSelect(context) {
}
}
} else if (datum && datum.__featurehash__ && !isMultiselect) { // clicked Data..
context
.selectedNoteID(null)
.enter(modeSelectData(context, datum));
} else if (datum instanceof osmNote && !isMultiselect) { // clicked a Note..
context
.selectedNoteID(datum.id)
.enter(modeSelectNote(context, datum.id));
} else { // clicked nothing..
context.selectedNoteID(null);
if (!isMultiselect && mode.id !== 'browse') {
context.enter(modeBrowse(context));
+2 -6
View File
@@ -30,9 +30,7 @@ export function modeBrowse(context) {
mode.enter = function() {
behaviors.forEach(function(behavior) {
context.install(behavior);
});
behaviors.forEach(context.install);
// Get focus on the body.
if (document.activeElement && document.activeElement.blur) {
@@ -49,9 +47,7 @@ export function modeBrowse(context) {
mode.exit = function() {
context.ui().sidebar.hover.cancel();
behaviors.forEach(function(behavior) {
context.uninstall(behavior);
});
behaviors.forEach(context.uninstall);
if (sidebar) {
context.ui().sidebar.hide();
+1
View File
@@ -11,4 +11,5 @@ export { modeMove } from './move';
export { modeRotate } from './rotate';
export { modeSave } from './save';
export { modeSelect } from './select';
export { modeSelectData } from './select_data';
export { modeSelectNote } from './select_note';
+2 -6
View File
@@ -120,9 +120,7 @@ export function modeRotate(context, entityIDs) {
mode.enter = function() {
behaviors.forEach(function(behavior) {
context.install(behavior);
});
behaviors.forEach(context.install);
context.surface()
.on('mousemove.rotate', doRotate)
@@ -141,9 +139,7 @@ export function modeRotate(context, entityIDs) {
mode.exit = function() {
behaviors.forEach(function(behavior) {
context.uninstall(behavior);
});
behaviors.forEach(context.uninstall);
context.surface()
.on('mousemove.rotate', null)
+2 -7
View File
@@ -451,9 +451,7 @@ export function modeSelect(context, selectedIDs) {
}
});
behaviors.forEach(function(behavior) {
context.install(behavior);
});
behaviors.forEach(context.install);
keybinding
.on(['[', 'pgup'], previousVertex)
@@ -522,10 +520,7 @@ export function modeSelect(context, selectedIDs) {
if (timeout) window.clearTimeout(timeout);
if (inspector) wrap.call(inspector.close);
behaviors.forEach(function(behavior) {
context.uninstall(behavior);
});
behaviors.forEach(context.uninstall);
keybinding.off();
closeMenu();
editMenu = undefined;
+97
View File
@@ -0,0 +1,97 @@
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
import {
behaviorBreathe,
behaviorHover,
behaviorLasso,
behaviorSelect
} from '../behavior';
import {
modeDragNode,
modeDragNote
} from '../modes';
import { modeBrowse } from './browse';
import { uiDataEditor } from '../ui';
export function modeSelectData(context, selectedDatum) {
var mode = {
id: 'select-data',
button: 'browse'
};
var keybinding = d3_keybinding('select-data');
var dataEditor = uiDataEditor(context);
var behaviors = [
behaviorBreathe(context),
behaviorHover(context),
behaviorSelect(context),
behaviorLasso(context),
modeDragNode(context).behavior,
modeDragNote(context).behavior
];
// class the data as selected, or return to browse mode if the data is gone
function selectData(drawn) {
var selection = context.surface().selectAll('.layer-mapdata .data' + selectedDatum.__featurehash__);
if (selection.empty()) {
// Return to browse mode if selected DOM elements have
// disappeared because the user moved them out of view..
var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent;
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
context.enter(modeBrowse(context));
}
} else {
selection.classed('selected', true);
}
}
function esc() {
context.enter(modeBrowse(context));
}
mode.enter = function() {
behaviors.forEach(context.install);
keybinding.on('⎋', esc, true);
d3_select(document).call(keybinding);
selectData();
context.ui().sidebar
.show(dataEditor.datum(selectedDatum));
context.map()
.on('drawn.select-data', selectData);
};
mode.exit = function() {
behaviors.forEach(context.uninstall);
keybinding.off();
context.surface()
.selectAll('.layer-mapdata .selected')
.classed('selected hover', false);
context.map()
.on('drawn.select-data', null);
context.ui().sidebar
.hide();
};
return mode;
}
+34 -41
View File
@@ -60,52 +60,48 @@ export function modeSelectNote(context, selectedNoteID) {
return note;
}
// class the note as selected, or return to browse mode if the note is gone
function selectNote(drawn) {
if (!checkSelectedID()) return;
var selection = context.surface().selectAll('.layer-notes .note-' + selectedNoteID);
if (selection.empty()) {
// Return to browse mode if selected DOM elements have
// disappeared because the user moved them out of view..
var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent;
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
context.enter(modeBrowse(context));
}
} else {
selection
.classed('selected', true);
context.selectedNoteID(selectedNoteID);
}
}
function esc() {
context.enter(modeBrowse(context));
}
mode.newFeature = function(_) {
if (!arguments.length) return newFeature;
newFeature = _;
return mode;
};
mode.enter = function() {
// class the note as selected, or return to browse mode if the note is gone
function selectNote(drawn) {
if (!checkSelectedID()) return;
var selection = context.surface()
.selectAll('.note-' + selectedNoteID);
if (selection.empty()) {
// Return to browse mode if selected DOM elements have
// disappeared because the user moved them out of view..
var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent;
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
context.enter(modeBrowse(context));
}
} else {
selection
.classed('selected', true);
context.selectedNoteID(selectedNoteID);
}
}
function esc() {
context.enter(modeBrowse(context));
}
var note = checkSelectedID();
if (!note) return;
behaviors.forEach(function(behavior) {
context.install(behavior);
});
keybinding
.on('⎋', esc, true);
d3_select(document)
.call(keybinding);
behaviors.forEach(context.install);
keybinding.on('⎋', esc, true);
d3_select(document).call(keybinding);
selectNote();
@@ -118,14 +114,11 @@ export function modeSelectNote(context, selectedNoteID) {
mode.exit = function() {
behaviors.forEach(function(behavior) {
context.uninstall(behavior);
});
behaviors.forEach(context.uninstall);
keybinding.off();
context.surface()
.selectAll('.note.selected')
.selectAll('.layer-notes .selected')
.classed('selected hover', false);
context.map()
+11 -29
View File
@@ -1,5 +1,4 @@
import _find from 'lodash-es/find';
import _omit from 'lodash-es/omit';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { interpolateNumber as d3_interpolateNumber } from 'd3-interpolate';
@@ -171,10 +170,10 @@ export function rendererBackground(context) {
.filter(function (d) { return !d.source().isLocatorOverlay() && !d.source().isHidden(); })
.forEach(function (d) { imageryUsed.push(d.source().imageryUsed()); });
var gpx = context.layers().layer('gpx');
if (gpx && gpx.enabled() && gpx.hasGpx()) {
var data = context.layers().layer('data');
if (data && data.enabled() && data.hasData()) {
// Include a string like '.gpx data file' or '.geojson data file'
var match = gpx.getSrc().match(/(kml|gpx|(?:geo)?json)$/i);
var match = data.getSrc().match(/(kml|gpx|pbf|mvt|(?:geo)?json)$/i);
var extension = match ? ('.' + match[0].toLowerCase() + ' ') : '';
imageryUsed.push(extension + 'data file');
}
@@ -184,14 +183,6 @@ export function rendererBackground(context) {
imageryUsed.push('Bing Streetside');
}
var mvt = context.layers().layer('mvt');
if (mvt && mvt.enabled() && mvt.hasMvt()) {
// Include a string like '.mvt data file' or '.geojson data file'
var matchmvt = mvt.getSrc().match(/(pbf|mvt|(?:geo)?json)$/i);
var extensionmvt = matchmvt ? ('.' + matchmvt[0].toLowerCase() + ' ') : '';
imageryUsed.push(extensionmvt + 'data file');
}
var mapillary_images = context.layers().layer('mapillary-images');
if (mapillary_images && mapillary_images.enabled()) {
imageryUsed.push('Mapillary Images');
@@ -219,7 +210,7 @@ export function rendererBackground(context) {
matchImagery.forEach(function(d) { matchIDs[d.id] = true; });
return _backgroundSources.filter(function(source) {
return matchIDs[source.id];
return matchIDs[source.id] || !source.polygon; // no polygon = worldwide
});
};
@@ -386,20 +377,18 @@ export function rendererBackground(context) {
data.imagery.features = {};
// build efficient index and querying for data.imagery
var world = [[[-180, -90], [-180, 90], [180, 90], [180, -90], [-180, -90]]];
var features = data.imagery.map(function(source) {
if (!source.polygon) return null;
var feature = {
type: 'Feature',
id: source.id,
properties: _omit(source, ['polygon']),
geometry: {
type: 'MultiPolygon',
coordinates: [ source.polygon || world ]
}
properties: { id: source.id },
geometry: { type: 'MultiPolygon', coordinates: [ source.polygon ] }
};
data.imagery.features[source.id] = feature;
return feature;
});
}).filter(Boolean);
data.imagery.query = whichPolygon({
type: 'FeatureCollection',
features: features
@@ -464,19 +453,12 @@ export function rendererBackground(context) {
});
if (q.gpx) {
var gpx = context.layers().layer('gpx');
var gpx = context.layers().layer('data');
if (gpx) {
gpx.url(q.gpx);
}
}
if (q.mvt) {
var mvt = context.layers().layer('mvt');
if (mvt) {
mvt.url(q.mvt);
}
}
if (q.offset) {
var offset = q.offset.replace(/;/g, ',').split(',').map(function(n) {
return !isNaN(n) && n;
+1 -1
View File
@@ -348,7 +348,7 @@ export function rendererMap(context) {
surface.selectAll('.layer-osm *').remove();
var mode = context.mode();
if (mode && mode.id !== 'save' && mode.id !== 'select-note') {
if (mode && mode.id !== 'save' && mode.id !== 'select-note' && mode.id !== 'select-data') {
context.enter(modeBrowse(context));
}
+3
View File
@@ -4,6 +4,7 @@ import serviceOpenstreetcam from './openstreetcam';
import serviceOsm from './osm';
import serviceStreetside from './streetside';
import serviceTaginfo from './taginfo';
import serviceVectorTile from './vector_tile';
import serviceWikidata from './wikidata';
import serviceWikipedia from './wikipedia';
@@ -14,6 +15,7 @@ export var services = {
osm: serviceOsm,
streetside: serviceStreetside,
taginfo: serviceTaginfo,
vectorTile: serviceVectorTile,
wikidata: serviceWikidata,
wikipedia: serviceWikipedia
};
@@ -25,6 +27,7 @@ export {
serviceOsm,
serviceStreetside,
serviceTaginfo,
serviceVectorTile,
serviceWikidata,
serviceWikipedia
};
+215
View File
@@ -0,0 +1,215 @@
import _clone from 'lodash-es/clone';
import _find from 'lodash-es/find';
import _isEqual from 'lodash-es/isEqual';
import _forEach from 'lodash-es/forEach';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { request as d3_request } from 'd3-request';
import turf_bboxClip from '@turf/bbox-clip';
import stringify from 'fast-json-stable-stringify';
import martinez from 'martinez-polygon-clipping';
import Protobuf from 'pbf';
import vt from '@mapbox/vector-tile';
import { utilHashcode, utilRebind, utilTiler } from '../util';
var tiler = utilTiler().tileSize(512).margin(1);
var dispatch = d3_dispatch('loadedData');
var _vtCache;
function abortRequest(i) {
i.abort();
}
function vtToGeoJSON(data, tile, mergeCache) {
var vectorTile = new vt.VectorTile(new Protobuf(data.response));
var layers = Object.keys(vectorTile.layers);
if (!Array.isArray(layers)) { layers = [layers]; }
var features = [];
layers.forEach(function(layerID) {
var layer = vectorTile.layers[layerID];
if (layer) {
for (var i = 0; i < layer.length; i++) {
var feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
var geometry = feature.geometry;
// Treat all Polygons as MultiPolygons
if (geometry.type === 'Polygon') {
geometry.type = 'MultiPolygon';
geometry.coordinates = [geometry.coordinates];
}
// Clip to tile bounds
if (geometry.type === 'MultiPolygon') {
var isClipped = false;
var featureClip = turf_bboxClip(feature, tile.extent.rectangle());
if (!_isEqual(feature.geometry, featureClip.geometry)) {
// feature = featureClip;
isClipped = true;
}
if (!feature.geometry.coordinates.length) continue; // not actually on this tile
if (!feature.geometry.coordinates[0].length) continue; // not actually on this tile
}
// Generate some unique IDs and add some metadata
var featurehash = utilHashcode(stringify(feature));
var propertyhash = utilHashcode(stringify(feature.properties || {}));
feature.__layerID__ = layerID.replace(/[^_a-zA-Z0-9\-]/g, '_');
feature.__featurehash__ = featurehash;
feature.__propertyhash__ = propertyhash;
features.push(feature);
// Clipped Polygons at same zoom with identical properties can get merged
if (isClipped && geometry.type === 'MultiPolygon') {
var merged = mergeCache[propertyhash];
if (merged && merged.length) {
var other = merged[0];
var coords = martinez.union(
feature.geometry.coordinates, other.geometry.coordinates
);
if (!coords || !coords.length) {
continue; // something failed in martinez union
}
merged.push(feature);
for (var j = 0; j < merged.length; j++) { // all these features get...
merged[j].geometry.coordinates = coords; // same coords
merged[j].__featurehash__ = featurehash; // same hash, so deduplication works
}
} else {
mergeCache[propertyhash] = [feature];
}
}
}
}
});
return features;
}
function loadTile(source, tile) {
if (source.loaded[tile.id] || source.inflight[tile.id]) return;
var url = source.template
.replace('{x}', tile.xyz[0])
.replace('{y}', tile.xyz[1])
// TMS-flipped y coordinate
.replace(/\{[t-]y\}/, Math.pow(2, tile.xyz[2]) - tile.xyz[1] - 1)
.replace(/\{z(oom)?\}/, tile.xyz[2])
.replace(/\{switch:([^}]+)\}/, function(s, r) {
var subdomains = r.split(',');
return subdomains[(tile.xyz[0] + tile.xyz[1]) % subdomains.length];
});
source.inflight[tile.id] = d3_request(url)
.responseType('arraybuffer')
.get(function(err, data) {
source.loaded[tile.id] = [];
delete source.inflight[tile.id];
if (err || !data) return;
var z = tile.xyz[2];
if (!source.canMerge[z]) {
source.canMerge[z] = {}; // initialize mergeCache
}
source.loaded[tile.id] = vtToGeoJSON(data, tile, source.canMerge[z]);
dispatch.call('loadedData');
});
}
export default {
init: function() {
if (!_vtCache) {
this.reset();
}
this.event = utilRebind(this, dispatch, 'on');
},
reset: function() {
for (var sourceID in _vtCache) {
var source = _vtCache[sourceID];
if (source && source.inflight) {
_forEach(source.inflight, abortRequest);
}
}
_vtCache = {};
},
addSource: function(sourceID, template) {
_vtCache[sourceID] = { template: template, inflight: {}, loaded: {}, canMerge: {} };
return _vtCache[sourceID];
},
data: function(sourceID, projection) {
var source = _vtCache[sourceID];
if (!source) return [];
var tiles = tiler.getTiles(projection);
var seen = {};
var results = [];
for (var i = 0; i < tiles.length; i++) {
var features = source.loaded[tiles[i].id];
if (!features || !features.length) continue;
for (var j = 0; j < features.length; j++) {
var feature = features[j];
var hash = feature.__featurehash__;
if (seen[hash]) continue;
seen[hash] = true;
// return a shallow clone, because the hash may change
// later if this feature gets merged with another
results.push(_clone(feature));
}
}
return results;
},
loadTiles: function(sourceID, template, projection) {
var source = _vtCache[sourceID];
if (!source) {
source = this.addSource(sourceID, template);
}
var tiles = tiler.getTiles(projection);
// abort inflight requests that are no longer needed
_forEach(source.inflight, function(v, k) {
var wanted = _find(tiles, function(tile) { return k === tile.id; });
if (!wanted) {
abortRequest(v);
delete source.inflight[k];
}
});
tiles.forEach(function(tile) {
loadTile(source, tile);
});
},
cache: function() {
return _vtCache;
}
};
+2 -2
View File
@@ -132,7 +132,7 @@ export function svgAreas(projection, context) {
fill: areas
};
var clipPaths = context.surface().selectAll('defs').selectAll('.clipPath')
var clipPaths = context.surface().selectAll('defs').selectAll('.clipPath-osm')
.filter(filter)
.data(data.clip, osmEntity.key);
@@ -141,7 +141,7 @@ export function svgAreas(projection, context) {
var clipPathsEnter = clipPaths.enter()
.append('clipPath')
.attr('class', 'clipPath')
.attr('class', 'clipPath-osm')
.attr('id', function(entity) { return entity.id + '-clippath'; });
clipPathsEnter
+512
View File
@@ -0,0 +1,512 @@
import _flatten from 'lodash-es/flatten';
import _isEmpty from 'lodash-es/isEmpty';
import _reduce from 'lodash-es/reduce';
import _union from 'lodash-es/union';
import _throttle from 'lodash-es/throttle';
import {
geoBounds as d3_geoBounds,
geoPath as d3_geoPath
} from 'd3-geo';
import { text as d3_text } from 'd3-request';
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import stringify from 'fast-json-stable-stringify';
import toGeoJSON from '@mapbox/togeojson';
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
import { services } from '../services';
import { svgPath } from './index';
import { utilDetect } from '../util/detect';
import { utilHashcode } from '../util';
var _initialized = false;
var _enabled = false;
var _geojson;
export function svgData(projection, context, dispatch) {
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
var _showLabels = true;
var detected = utilDetect();
var layer = d3_select(null);
var _vtService;
var _fileList;
var _template;
var _src;
function init() {
if (_initialized) return; // run once
_geojson = {};
_enabled = true;
function over() {
d3_event.stopPropagation();
d3_event.preventDefault();
d3_event.dataTransfer.dropEffect = 'copy';
}
d3_select('body')
.attr('dropzone', 'copy')
.on('drop.svgData', function() {
d3_event.stopPropagation();
d3_event.preventDefault();
if (!detected.filedrop) return;
drawData.fileList(d3_event.dataTransfer.files);
})
.on('dragenter.svgData', over)
.on('dragexit.svgData', over)
.on('dragover.svgData', over);
_initialized = true;
}
function getService() {
if (services.vectorTile && !_vtService) {
_vtService = services.vectorTile;
_vtService.event.on('loadedData', throttledRedraw);
} else if (!services.vectorTile && _vtService) {
_vtService = null;
}
return _vtService;
}
function showLayer() {
layerOn();
layer
.style('opacity', 0)
.transition()
.duration(250)
.style('opacity', 1)
.on('end', function () { dispatch.call('change'); });
}
function hideLayer() {
throttledRedraw.cancel();
layer
.transition()
.duration(250)
.style('opacity', 0)
.on('end', layerOff);
}
function layerOn() {
layer.style('display', 'block');
}
function layerOff() {
layer.selectAll('.viewfield-group').remove();
layer.style('display', 'none');
}
// ensure that all geojson features in a collection have IDs
function ensureIDs(gj) {
if (!gj) return null;
if (gj.type === 'FeatureCollection') {
for (var i = 0; i < gj.features.length; i++) {
ensureFeatureID(gj.features[i]);
}
} else {
ensureFeatureID(gj);
}
return gj;
}
// ensure that each single Feature object has a unique ID
function ensureFeatureID(feature) {
if (!feature) return;
feature.__featurehash__ = utilHashcode(stringify(feature));
return feature;
}
// Prefer an array of Features instead of a FeatureCollection
function getFeatures(gj) {
if (!gj) return [];
if (gj.type === 'FeatureCollection') {
return gj.features;
} else {
return [gj];
}
}
function featureKey(d) {
return d.__featurehash__;
}
function isPolygon(d) {
return d.geometry.type === 'Polygon' || d.geometry.type === 'MultiPolygon';
}
function clipPathID(d) {
return 'data-' + d.__featurehash__ + '-clippath';
}
function featureClasses(d) {
return [
'data' + d.__featurehash__,
d.geometry.type,
isPolygon(d) ? 'area' : '',
d.__layerID__ || ''
].filter(Boolean).join(' ');
}
function drawData(selection) {
var vtService = getService();
var getPath = svgPath(projection).geojson;
var getAreaPath = svgPath(projection, null, true).geojson;
var hasData = drawData.hasData();
layer = selection.selectAll('.layer-mapdata')
.data(_enabled && hasData ? [0] : []);
layer.exit()
.remove();
layer = layer.enter()
.append('g')
.attr('class', 'layer-mapdata')
.merge(layer);
var surface = context.surface();
if (!surface || surface.empty()) return; // not ready to draw yet, starting up
// Gather data
var geoData, polygonData;
if (_template && vtService) { // fetch data from vector tile service
var sourceID = _template;
vtService.loadTiles(sourceID, _template, projection);
geoData = vtService.data(sourceID, projection);
} else {
geoData = getFeatures(_geojson);
}
geoData = geoData.filter(getPath);
polygonData = geoData.filter(isPolygon);
// Draw clip paths for polygons
var clipPaths = surface.selectAll('defs').selectAll('.clipPath-data')
.data(polygonData, featureKey);
clipPaths.exit()
.remove();
var clipPathsEnter = clipPaths.enter()
.append('clipPath')
.attr('class', 'clipPath-data')
.attr('id', clipPathID);
clipPathsEnter
.append('path');
clipPaths.merge(clipPathsEnter)
.selectAll('path')
.attr('d', getAreaPath);
// Draw fill, shadow, stroke layers
var datagroups = layer
.selectAll('g.datagroup')
.data(['fill', 'shadow', 'stroke']);
datagroups = datagroups.enter()
.append('g')
.attr('class', function(d) { return 'datagroup datagroup-' + d; })
.merge(datagroups);
// Draw paths
var pathData = {
fill: polygonData,
shadow: geoData,
stroke: geoData
};
var paths = datagroups
.selectAll('path')
.data(function(layer) { return pathData[layer]; }, featureKey);
// exit
paths.exit()
.remove();
// enter/update
paths = paths.enter()
.append('path')
.attr('class', function(d) {
var datagroup = this.parentNode.__data__;
return 'pathdata ' + datagroup + ' ' + featureClasses(d);
})
.attr('clip-path', function(d) {
var datagroup = this.parentNode.__data__;
return datagroup === 'fill' ? ('url(#' + clipPathID(d) + ')') : null;
})
.merge(paths)
.attr('d', function(d) {
var datagroup = this.parentNode.__data__;
return datagroup === 'fill' ? getAreaPath(d) : getPath(d);
});
// Draw labels
layer
.call(drawLabels, 'label-halo', geoData)
.call(drawLabels, 'label', geoData);
function drawLabels(selection, textClass, data) {
var labelPath = d3_geoPath(projection);
var labelData = data.filter(function(d) {
return _showLabels && d.properties && (d.properties.desc || d.properties.name);
});
var labels = selection.selectAll('text.' + textClass)
.data(labelData, featureKey);
// exit
labels.exit()
.remove();
// enter/update
labels = labels.enter()
.append('text')
.attr('class', function(d) { return textClass + ' ' + featureClasses(d); })
.merge(labels)
.text(function(d) {
return d.properties.desc || d.properties.name;
})
.attr('x', function(d) {
var centroid = labelPath.centroid(d);
return centroid[0] + 11;
})
.attr('y', function(d) {
var centroid = labelPath.centroid(d);
return centroid[1];
});
}
}
function getExtension(fileName) {
if (!fileName) return;
var lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex < 0) return;
return fileName.substr(lastDotIndex);
}
function xmlToDom(textdata) {
return (new DOMParser()).parseFromString(textdata, 'text/xml');
}
drawData.setFile = function(extension, data, src) {
_template = null;
_fileList = null;
_geojson = null;
_src = null;
var gj;
switch (extension) {
case '.gpx':
gj = toGeoJSON.gpx(xmlToDom(data));
break;
case '.kml':
gj = toGeoJSON.kml(xmlToDom(data));
break;
case '.geojson':
case '.json':
gj = JSON.parse(data);
break;
}
if (!_isEmpty(gj)) {
_geojson = ensureIDs(gj);
_src = src || 'unknown.geojson';
this.fitZoom();
}
dispatch.call('change');
return this;
};
drawData.showLabels = function(val) {
if (!arguments.length) return _showLabels;
_showLabels = val;
return this;
};
drawData.enabled = function(val) {
if (!arguments.length) return _enabled;
_enabled = val;
if (_enabled) {
showLayer();
} else {
hideLayer();
}
dispatch.call('change');
return this;
};
drawData.hasData = function() {
return !!(_template || !_isEmpty(_geojson));
};
drawData.template = function(val) {
if (!arguments.length) return _template;
_template = val;
_fileList = null;
_geojson = null;
_src = 'vector tiles';
dispatch.call('change');
return this;
};
drawData.geojson = function(gj, src) {
if (!arguments.length) return _geojson;
_template = null;
_fileList = null;
_geojson = null;
_src = null;
if (!_isEmpty(gj)) {
_geojson = ensureIDs(gj);
_src = src || 'unknown.geojson';
}
dispatch.call('change');
return this;
};
drawData.fileList = function(fileList) {
if (!arguments.length) return _fileList;
_template = null;
_fileList = fileList;
_geojson = null;
_src = null;
if (!fileList || !fileList.length) return this;
var f = fileList[0];
var extension = getExtension(f.name);
var reader = new FileReader();
reader.onload = (function(file) {
return function(e) {
drawData.setFile(extension, e.target.result, file.name);
};
})(f);
reader.readAsText(f);
return this;
};
drawData.url = function(url) {
_template = null;
_fileList = null;
_geojson = null;
_src = null;
var extension = getExtension(url);
var re = /\.(gpx|kml|(geo)?json)$/i;
if (re.test(extension)) {
_template = null;
d3_text(url, function(err, data) {
if (err) return;
drawData.setFile(extension, data, url);
});
} else {
drawData.template(url);
}
return this;
};
drawData.getSrc = function() {
return _src || '';
};
drawData.fitZoom = function() {
var features = getFeatures(_geojson);
if (!features.length) return;
var map = context.map();
var viewport = map.trimmedExtent().polygon();
var coords = _reduce(features, function(coords, feature) {
var c = feature.geometry.coordinates;
/* eslint-disable no-fallthrough */
switch (feature.geometry.type) {
case 'Point':
c = [c];
case 'MultiPoint':
case 'LineString':
break;
case 'MultiPolygon':
c = _flatten(c);
case 'Polygon':
case 'MultiLineString':
c = _flatten(c);
break;
}
/* eslint-enable no-fallthrough */
return _union(coords, c);
}, []);
if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
var extent = geoExtent(d3_geoBounds({ type: 'LineString', coordinates: coords }));
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
}
return this;
};
init();
return drawData;
}
-266
View File
@@ -1,266 +0,0 @@
import _flatten from 'lodash-es/flatten';
import _isEmpty from 'lodash-es/isEmpty';
import _reduce from 'lodash-es/reduce';
import _union from 'lodash-es/union';
import { geoBounds as d3_geoBounds } from 'd3-geo';
import { text as d3_text } from 'd3-request';
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
import { svgPath } from './index';
import { utilDetect } from '../util/detect';
import toGeoJSON from '@mapbox/togeojson';
var _initialized = false;
var _enabled = false;
var _geojson;
export function svgGpx(projection, context, dispatch) {
var _showLabels = true;
var detected = utilDetect();
var layer;
var _src;
function init() {
if (_initialized) return; // run once
_geojson = {};
_enabled = true;
function over() {
d3_event.stopPropagation();
d3_event.preventDefault();
d3_event.dataTransfer.dropEffect = 'copy';
}
d3_select('body')
.attr('dropzone', 'copy')
.on('drop.localgpx', function() {
d3_event.stopPropagation();
d3_event.preventDefault();
if (!detected.filedrop) return;
drawGpx.files(d3_event.dataTransfer.files);
})
.on('dragenter.localgpx', over)
.on('dragexit.localgpx', over)
.on('dragover.localgpx', over);
_initialized = true;
}
function drawGpx(selection) {
var getPath = svgPath(projection).geojson;
layer = selection.selectAll('.layer-gpx')
.data(_enabled ? [0] : []);
layer.exit()
.remove();
layer = layer.enter()
.append('g')
.attr('class', 'layer-gpx')
.merge(layer);
var paths = layer
.selectAll('path')
.data([_geojson]);
paths.exit()
.remove();
paths = paths.enter()
.append('path')
.attr('class', 'gpx')
.merge(paths);
paths
.attr('d', getPath);
var labelData = _showLabels && _geojson.features ? _geojson.features : [];
labelData = labelData.filter(getPath);
layer
.call(drawLabels, 'gpxlabel-halo', labelData)
.call(drawLabels, 'gpxlabel', labelData);
function drawLabels(selection, textClass, data) {
var labels = selection.selectAll('text.' + textClass)
.data(data);
// exit
labels.exit()
.remove();
// enter/update
labels = labels.enter()
.append('text')
.attr('class', textClass)
.merge(labels)
.text(function(d) {
if (d.properties) {
return d.properties.desc || d.properties.name;
}
return null;
})
.attr('x', function(d) {
var centroid = getPath.centroid(d);
return centroid[0] + 11;
})
.attr('y', function(d) {
var centroid = getPath.centroid(d);
return centroid[1];
});
}
}
function toDom(x) {
return (new DOMParser()).parseFromString(x, 'text/xml');
}
function getExtension(fileName) {
if (fileName === undefined) {
return '';
}
var lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex < 0) {
return '';
}
return fileName.substr(lastDotIndex);
}
function parseSaveAndZoom(extension, data, src) {
switch (extension) {
default:
drawGpx.geojson(toGeoJSON.gpx(toDom(data)), src).fitZoom();
break;
case '.kml':
drawGpx.geojson(toGeoJSON.kml(toDom(data)), src).fitZoom();
break;
case '.geojson':
case '.json':
drawGpx.geojson(JSON.parse(data), src).fitZoom();
break;
}
}
drawGpx.showLabels = function(_) {
if (!arguments.length) return _showLabels;
_showLabels = _;
return this;
};
drawGpx.enabled = function(_) {
if (!arguments.length) return _enabled;
_enabled = _;
dispatch.call('change');
return this;
};
drawGpx.hasGpx = function() {
return (!(_isEmpty(_geojson) || _isEmpty(_geojson.features)));
};
drawGpx.geojson = function(gj, src) {
if (!arguments.length) return _geojson;
if (_isEmpty(gj) || _isEmpty(gj.features)) return this;
_geojson = gj;
_src = src || 'unknown.geojson';
dispatch.call('change');
return this;
};
drawGpx.url = function(url) {
d3_text(url, function(err, data) {
if (!err) {
var extension = getExtension(url);
parseSaveAndZoom(extension, data, url);
}
});
return this;
};
drawGpx.files = function(fileList) {
if (!fileList.length) return this;
var f = fileList[0];
var reader = new FileReader();
reader.onload = (function(file) {
var extension = getExtension(file.name);
return function (e) {
parseSaveAndZoom(extension, e.target.result, file.name);
};
})(f);
reader.readAsText(f);
return this;
};
drawGpx.getSrc = function () {
return _src;
};
drawGpx.fitZoom = function() {
if (!this.hasGpx()) return this;
var map = context.map();
var viewport = map.trimmedExtent().polygon();
var coords = _reduce(_geojson.features, function(coords, feature) {
var c = feature.geometry.coordinates;
/* eslint-disable no-fallthrough */
switch (feature.geometry.type) {
case 'Point':
c = [c];
case 'MultiPoint':
case 'LineString':
break;
case 'MultiPolygon':
c = _flatten(c);
case 'Polygon':
case 'MultiLineString':
c = _flatten(c);
break;
}
/* eslint-enable no-fallthrough */
return _union(coords, c);
}, []);
if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
var extent = geoExtent(d3_geoBounds({ type: 'LineString', coordinates: coords }));
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
}
return this;
};
init();
return drawGpx;
}
+11 -1
View File
@@ -168,7 +168,17 @@ export function svgPath(projection, graph, isArea) {
}
};
svgpath.geojson = path;
svgpath.geojson = function(d) {
if (d.__featurehash__ !== undefined) {
if (d.__featurehash__ in cache) {
return cache[d.__featurehash__];
} else {
return cache[d.__featurehash__] = path(d);
}
} else {
return path(d);
}
};
return svgpath;
}
+1 -2
View File
@@ -1,8 +1,7 @@
export { svgAreas } from './areas.js';
export { svgData } from './data.js';
export { svgDebug } from './debug.js';
export { svgDefs } from './defs.js';
export { svgGpx } from './gpx.js';
export { svgMvt } from './mvt.js';
export { svgIcon } from './icon.js';
export { svgLabels } from './labels.js';
export { svgLayers } from './layers.js';
+2 -4
View File
@@ -7,10 +7,9 @@ import _reject from 'lodash-es/reject';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import { svgData } from './data';
import { svgDebug } from './debug';
import { svgGpx } from './gpx';
import { svgStreetside } from './streetside';
import { svgMvt } from './mvt';
import { svgMapillaryImages } from './mapillary_images';
import { svgMapillarySigns } from './mapillary_signs';
import { svgOpenstreetcamImages } from './openstreetcam_images';
@@ -26,8 +25,7 @@ export function svgLayers(projection, context) {
var layers = [
{ id: 'osm', layer: svgOsm(projection, context, dispatch) },
{ id: 'notes', layer: svgNotes(projection, context, dispatch) },
{ id: 'gpx', layer: svgGpx(projection, context, dispatch) },
{ id: 'mvt', layer: svgMvt(projection, context, dispatch) },
{ id: 'data', layer: svgData(projection, context, dispatch) },
{ id: 'streetside', layer: svgStreetside(projection, context, dispatch)},
{ id: 'mapillary-images', layer: svgMapillaryImages(projection, context, dispatch) },
{ id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) },
-301
View File
@@ -1,301 +0,0 @@
import _flatten from 'lodash-es/flatten';
import _isEmpty from 'lodash-es/isEmpty';
import _reduce from 'lodash-es/reduce';
import _union from 'lodash-es/union';
import { geoBounds as d3_geoBounds } from 'd3-geo';
import { request as d3_request } from 'd3-request';
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import vt from '@mapbox/vector-tile';
import Protobuf from 'pbf';
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
import { svgPath } from './index';
import { utilDetect } from '../util/detect';
var _initialized = false;
var _enabled = false;
var _geojson;
export function svgMvt(projection, context, dispatch) {
var _showLabels = true;
var detected = utilDetect();
var layer;
var _src;
function init() {
if (_initialized) return; // run once
_geojson = {};
_enabled = true;
function over() {
d3_event.stopPropagation();
d3_event.preventDefault();
d3_event.dataTransfer.dropEffect = 'copy';
}
d3_select('body')
.attr('dropzone', 'copy')
.on('drop.localmvt', function() {
d3_event.stopPropagation();
d3_event.preventDefault();
if (!detected.filedrop) return;
drawMvt.files(d3_event.dataTransfer.files);
})
.on('dragenter.localmvt', over)
.on('dragexit.localmvt', over)
.on('dragover.localmvt', over);
_initialized = true;
}
function drawMvt(selection) {
var getPath = svgPath(projection).geojson;
layer = selection.selectAll('.layer-mvt')
.data(_enabled ? [0] : []);
layer.exit()
.remove();
layer = layer.enter()
.append('g')
.attr('class', 'layer-mvt')
.merge(layer);
var paths = layer
.selectAll('path')
.data([_geojson]);
paths.exit()
.remove();
paths = paths.enter()
.append('path')
.attr('class', 'mvt')
.merge(paths);
paths
.attr('d', getPath);
var labelData = _showLabels && _geojson.features ? _geojson.features : [];
labelData = labelData.filter(getPath);
layer
.call(drawLabels, 'mvtlabel-halo', labelData)
.call(drawLabels, 'mvtlabel', labelData);
function drawLabels(selection, textClass, data) {
var labels = selection.selectAll('text.' + textClass)
.data(data);
// exit
labels.exit()
.remove();
// enter/update
labels = labels.enter()
.append('text')
.attr('class', textClass)
.merge(labels)
.text(function(d) {
if (d.properties) {
return d.properties.desc || d.properties.name;
}
return null;
})
.attr('x', function(d) {
var centroid = getPath.centroid(d);
return centroid[0] + 11;
})
.attr('y', function(d) {
var centroid = getPath.centroid(d);
return centroid[1];
});
}
}
function vtToGeoJson(bufferdata) {
var tile = new vt.VectorTile(new Protobuf(bufferdata.data.response));
var layers = Object.keys(tile.layers);
if (!Array.isArray(layers)) { layers = [layers]; }
var collection = {type: 'FeatureCollection', features: []};
layers.forEach(function (layerID) {
var layer = tile.layers[layerID];
if (layer) {
for (var i = 0; i < layer.length; i++) {
var feature = layer.feature(i).toGeoJSON(bufferdata.zxy[2], bufferdata.zxy[3], bufferdata.zxy[1]);
if (layers.length > 1) feature.properties.vt_layer = layerID;
collection.features.push(feature);
}
}
});
return collection;
}
function getExtension(fileName) {
if (fileName === undefined) {
return '';
}
var lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex < 0) {
return '';
}
return fileName.substr(lastDotIndex);
}
function parseSaveAndZoom(extension, bufferdata) {
switch (extension) {
case '.pbf':
drawMvt.geojson(vtToGeoJson(bufferdata)).fitZoom();
break;
case '.mvt':
drawMvt.geojson(vtToGeoJson(bufferdata)).fitZoom();
break;
}
}
drawMvt.showLabels = function(_) {
if (!arguments.length) return _showLabels;
_showLabels = _;
return this;
};
drawMvt.enabled = function(_) {
if (!arguments.length) return _enabled;
_enabled = _;
dispatch.call('change');
return this;
};
drawMvt.hasMvt = function() {
return (!(_isEmpty(_geojson) || _isEmpty(_geojson.features)));
};
drawMvt.geojson = function(gj) {
if (!arguments.length) return _geojson;
if (_isEmpty(gj) || _isEmpty(gj.features)) return this;
_geojson = gj;
dispatch.call('change');
return this;
};
drawMvt.url = function(url) {
d3_request(url)
.responseType('arraybuffer')
.get(function(err, data) {
if (err || !data) return;
_src = url;
var match = url.match(/(pbf|mvt)/i);
var extension = match ? ('.' + match[0].toLowerCase()) : '';
var zxy = url.match(/\/(\d+)\/(\d+)\/(\d+)/);
var bufferdata = {
data : data,
zxy : zxy
};
parseSaveAndZoom(extension, bufferdata);
});
return this;
};
drawMvt.files = function(fileList) {
if (!fileList.length) return this;
var f = fileList[0],
reader = new FileReader();
reader.onload = (function(file) {
return; // todo find x,y,z
var data = [];
var zxy = [0,0,0];
_src = file.name;
var extension = getExtension(file.name);
var bufferdata = {
data: data,
zxy: zxy
};
return function (e) {
bufferdata.data = e.target.result;
parseSaveAndZoom(extension, bufferdata);
};
})(f);
reader.readAsArrayBuffer(f);
return this;
};
drawMvt.getSrc = function () {
return _src;
};
drawMvt.fitZoom = function() {
if (!this.hasMvt()) return this;
var map = context.map();
var viewport = map.trimmedExtent().polygon();
var coords = _reduce(_geojson.features, function(coords, feature) {
var c = feature.geometry.coordinates;
/* eslint-disable no-fallthrough */
switch (feature.geometry.type) {
case 'Point':
c = [c];
case 'MultiPoint':
case 'LineString':
break;
case 'MultiPolygon':
c = _flatten(c);
case 'Polygon':
case 'MultiLineString':
c = _flatten(c);
break;
}
/* eslint-enable no-fallthrough */
return _union(coords, c);
}, []);
if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
var extent = geoExtent(d3_geoBounds({ type: 'LineString', coordinates: coords }));
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
}
return this;
};
init();
return drawMvt;
}
+79
View File
@@ -0,0 +1,79 @@
import { t } from '../util/locale';
import { modeBrowse } from '../modes';
import { svgIcon } from '../svg';
import {
uiDataHeader,
uiRawTagEditor
} from './index';
export function uiDataEditor(context) {
var dataHeader = uiDataHeader();
var rawTagEditor = uiRawTagEditor(context);
var _datum;
function dataEditor(selection) {
var header = selection.selectAll('.header')
.data([0]);
var headerEnter = header.enter()
.append('div')
.attr('class', 'header fillL');
headerEnter
.append('button')
.attr('class', 'fr data-editor-close')
.on('click', function() {
context.enter(modeBrowse(context));
})
.call(svgIcon('#iD-icon-close'));
headerEnter
.append('h3')
.text(t('map_data.title'));
var body = selection.selectAll('.body')
.data([0]);
body = body.enter()
.append('div')
.attr('class', 'body')
.merge(body);
var editor = body.selectAll('.data-editor')
.data([0]);
editor.enter()
.append('div')
.attr('class', 'modal-section data-editor')
.merge(editor)
.call(dataHeader.datum(_datum));
var rte = body.selectAll('.raw-tag-editor')
.data([0]);
rte.enter()
.append('div')
.attr('class', 'inspector-border raw-tag-editor inspector-inner data-editor')
.merge(rte)
.call(rawTagEditor
.expanded(true)
.readOnlyTags([/./])
.tags((_datum && _datum.properties) || {})
.state('hover')
);
}
dataEditor.datum = function(val) {
if (!arguments.length) return _datum;
_datum = val;
return this;
};
return dataEditor;
}
+47
View File
@@ -0,0 +1,47 @@
import { t } from '../util/locale';
import { svgIcon } from '../svg';
export function uiDataHeader() {
var _datum;
function dataHeader(selection) {
var header = selection.selectAll('.data-header')
.data(
(_datum ? [_datum] : []),
function(d) { return d.__featurehash__; }
);
header.exit()
.remove();
var headerEnter = header.enter()
.append('div')
.attr('class', 'data-header');
var iconEnter = headerEnter
.append('div')
.attr('class', 'data-header-icon');
iconEnter
.append('div')
.attr('class', 'preset-icon-28')
.call(svgIcon('#iD-icon-data', 'note-fill'));
headerEnter
.append('div')
.attr('class', 'data-header-label')
.text(t('map_data.layers.custom.title'));
}
dataHeader.datum = function(val) {
if (!arguments.length) return _datum;
_datum = val;
return this;
};
return dataHeader;
}
+2
View File
@@ -13,6 +13,8 @@ export { uiConfirm } from './confirm';
export { uiConflicts } from './conflicts';
export { uiContributors } from './contributors';
export { uiCurtain } from './curtain';
export { uiDataEditor } from './data_editor';
export { uiDataHeader } from './data_header';
export { uiDisclosure } from './disclosure';
export { uiEditMenu } from './edit_menu';
export { uiEntityEditor } from './entity_editor';
+48 -117
View File
@@ -12,6 +12,7 @@ import { modeBrowse } from '../modes';
import { uiBackground } from './background';
import { uiDisclosure } from './disclosure';
import { uiHelp } from './help';
import { uiSettingsCustomData } from './settings/custom_data';
import { uiTooltipHtml } from './tooltipHtml';
@@ -21,6 +22,9 @@ export function uiMapData(context) {
var layers = context.layers();
var fills = ['wireframe', 'partial', 'full'];
var settingsCustomData = uiSettingsCustomData(context)
.on('change', customChanged);
var _fillSelected = context.storage('area-fill') || 'partial';
var _shown = false;
var _dataLayerContainer = d3_select(null);
@@ -207,14 +211,14 @@ export function uiMapData(context) {
}
function drawGpxItem(selection) {
var gpx = layers.layer('gpx');
var hasGpx = gpx && gpx.hasGpx();
var showsGpx = hasGpx && gpx.enabled();
function drawCustomDataItems(selection) {
var dataLayer = layers.layer('data');
var hasData = dataLayer && dataLayer.hasData();
var showsData = hasData && dataLayer.enabled();
var ul = selection
.selectAll('.layer-list-gpx')
.data(gpx ? [0] : []);
.selectAll('.layer-list-data')
.data(dataLayer ? [0] : []);
// Exit
ul.exit()
@@ -223,154 +227,82 @@ export function uiMapData(context) {
// Enter
var ulEnter = ul.enter()
.append('ul')
.attr('class', 'layer-list layer-list-gpx');
.attr('class', 'layer-list layer-list-data');
var liEnter = ulEnter
.append('li')
.attr('class', 'list-item-gpx');
.attr('class', 'list-item-data');
liEnter
.append('button')
.attr('class', 'list-item-gpx-extent')
.call(tooltip()
.title(t('gpx.zoom'))
.title(t('settings.custom_data.tooltip'))
.placement((textDirection === 'rtl') ? 'right' : 'left')
)
.on('click', editCustom)
.call(svgIcon('#iD-icon-more'));
liEnter
.append('button')
.call(tooltip()
.title(t('map_data.layers.custom.zoom'))
.placement((textDirection === 'rtl') ? 'right' : 'left')
)
.on('click', function() {
d3_event.preventDefault();
d3_event.stopPropagation();
gpx.fitZoom();
dataLayer.fitZoom();
})
.call(svgIcon('#iD-icon-search'));
liEnter
.append('button')
.attr('class', 'list-item-gpx-browse')
.call(tooltip()
.title(t('gpx.browse'))
.placement((textDirection === 'rtl') ? 'right' : 'left')
)
.on('click', function() {
d3_select(document.createElement('input'))
.attr('type', 'file')
.on('change', function() {
gpx.files(d3_event.target.files);
})
.node().click();
})
.call(svgIcon('#iD-icon-geolocate'));
var labelEnter = liEnter
.append('label')
.call(tooltip()
.title(t('gpx.drag_drop'))
.title(t('map_data.layers.custom.tooltip'))
.placement('top')
);
labelEnter
.append('input')
.attr('type', 'checkbox')
.on('change', function() { toggleLayer('gpx'); });
.on('change', function() { toggleLayer('data'); });
labelEnter
.append('span')
.text(t('gpx.local_layer'));
.text(t('map_data.layers.custom.title'));
// Update
ul = ul
.merge(ulEnter);
ul.selectAll('.list-item-gpx')
.classed('active', showsGpx)
ul.selectAll('.list-item-data')
.classed('active', showsData)
.selectAll('label')
.classed('deemphasize', !hasGpx)
.classed('deemphasize', !hasData)
.selectAll('input')
.property('disabled', !hasGpx)
.property('checked', showsGpx);
.property('disabled', !hasData)
.property('checked', showsData);
}
function drawMvtItem(selection) {
var mvt = layers.layer('mvt'),
hasMvt = mvt && mvt.hasMvt(),
showsMvt = hasMvt && mvt.enabled();
var ul = selection
.selectAll('.layer-list-mvt')
.data(mvt ? [0] : []);
// Exit
ul.exit()
.remove();
// Enter
var ulEnter = ul.enter()
.append('ul')
.attr('class', 'layer-list layer-list-mvt');
var liEnter = ulEnter
.append('li')
.attr('class', 'list-item-mvt');
liEnter
.append('button')
.attr('class', 'list-item-mvt-extent')
.call(tooltip()
.title(t('mvt.zoom'))
.placement((textDirection === 'rtl') ? 'right' : 'left')
)
.on('click', function() {
d3_event.preventDefault();
d3_event.stopPropagation();
mvt.fitZoom();
})
.call(svgIcon('#iD-icon-search'));
liEnter
.append('button')
.attr('class', 'list-item-mvt-browse')
.call(tooltip()
.title(t('mvt.browse'))
.placement((textDirection === 'rtl') ? 'right' : 'left')
)
.on('click', function() {
d3_select(document.createElement('input'))
.attr('type', 'file')
.on('change', function() {
mvt.files(d3_event.target.files);
})
.node().click();
})
.call(svgIcon('#iD-icon-geolocate'));
var labelEnter = liEnter
.append('label')
.call(tooltip()
.title(t('mvt.drag_drop'))
.placement('top')
);
labelEnter
.append('input')
.attr('type', 'checkbox')
.on('change', function() { toggleLayer('mvt'); });
labelEnter
.append('span')
.text(t('mvt.local_layer'));
// Update
ul = ul
.merge(ulEnter);
ul.selectAll('.list-item-mvt')
.classed('active', showsMvt)
.selectAll('label')
.classed('deemphasize', !hasMvt)
.selectAll('input')
.property('disabled', !hasMvt)
.property('checked', showsMvt);
function editCustom() {
d3_event.preventDefault();
context.container()
.call(settingsCustomData);
}
function customChanged(d) {
var dataLayer = layers.layer('data');
if (d && d.url) {
dataLayer.url(d.url);
} else if (d && d.fileList) {
dataLayer.fileList(d.fileList);
}
}
function drawListItems(selection, data, type, name, change, active) {
var items = selection.selectAll('li')
.data(data);
@@ -462,8 +394,7 @@ export function uiMapData(context) {
_dataLayerContainer
.call(drawOsmItems)
.call(drawPhotoItems)
.call(drawGpxItem);
// .call(drawMvtItem);
.call(drawCustomDataItems);
_fillList
.call(drawListItems, fills, 'radio', 'area_fill', setFill, showsFill);
+3 -3
View File
@@ -22,7 +22,7 @@ import {
} from '../geo';
import { rendererTileLayer } from '../renderer';
import { svgDebug, svgGpx } from '../svg';
import { svgDebug, svgData } from '../svg';
import { utilSetTransform } from '../util';
import { utilGetDimensions } from '../util/dimensions';
@@ -33,7 +33,7 @@ export function uiMapInMap(context) {
var backgroundLayer = rendererTileLayer(context);
var overlayLayers = {};
var projection = geoRawMercator();
var gpxLayer = svgGpx(projection, context).showLabels(false);
var dataLayer = svgData(projection, context).showLabels(false);
var debugLayer = svgDebug(projection, context);
var zoom = d3_zoom()
.scaleExtent([geoZoomToScale(0.5), geoZoomToScale(24)])
@@ -242,7 +242,7 @@ export function uiMapInMap(context) {
.append('svg')
.attr('class', 'map-in-map-data')
.merge(dataLayers)
.call(gpxLayer)
.call(dataLayer)
.call(debugLayer);
+29 -30
View File
@@ -24,17 +24,17 @@ import {
export function uiRawTagEditor(context) {
var taginfo = services.taginfo,
dispatch = d3_dispatch('change'),
_readOnlyTags = [],
_showBlank = false,
_updatePreference = true,
_expanded = false,
_newRow,
_state,
_preset,
_tags,
_entityID;
var taginfo = services.taginfo;
var dispatch = d3_dispatch('change');
var _readOnlyTags = [];
var _showBlank = false;
var _updatePreference = true;
var _expanded = false;
var _newRow;
var _state;
var _preset;
var _tags;
var _entityID;
function rawTagEditor(selection) {
@@ -148,16 +148,16 @@ export function uiRawTagEditor(context) {
items
.each(function(tag) {
var row = d3_select(this),
key = row.select('input.key'), // propagate bound data to child
value = row.select('input.value'); // propagate bound data to child
var row = d3_select(this);
var key = row.select('input.key'); // propagate bound data to child
var value = row.select('input.value'); // propagate bound data to child
if (_entityID && taginfo) {
bindTypeahead(key, value);
}
var isRelation = (_entityID && context.entity(_entityID).type === 'relation'),
reference;
var isRelation = (_entityID && context.entity(_entityID).type === 'relation');
var reference;
if (isRelation && tag.key === 'type') {
reference = uiTagReference({ rtype: tag.value }, context);
@@ -239,8 +239,8 @@ export function uiRawTagEditor(context) {
function sort(value, data) {
var sameletter = [],
other = [];
var sameletter = [];
var other = [];
for (var i = 0; i < data.length; i++) {
if (data[i].value.substring(0, value.length) === value) {
sameletter.push(data[i]);
@@ -265,10 +265,9 @@ export function uiRawTagEditor(context) {
function keyChange(d) {
var kOld = d.key,
kNew = this.value.trim(),
tag = {};
var kOld = d.key;
var kNew = this.value.trim();
var tag = {};
if (isReadOnly({ key: kNew })) {
this.value = kOld;
@@ -276,17 +275,17 @@ export function uiRawTagEditor(context) {
}
if (kNew && kNew !== kOld) {
var match = kNew.match(/^(.*?)(?:_(\d+))?$/),
base = match[1],
suffix = +(match[2] || 1);
var match = kNew.match(/^(.*?)(?:_(\d+))?$/);
var base = match[1];
var suffix = +(match[2] || 1);
while (_tags[kNew]) { // rename key if already in use
kNew = base + '_' + suffix++;
}
if (_includes(kNew, '=')) {
var splitStr = kNew.split('=').map(function(str) { return str.trim(); }),
key = splitStr[0],
value = splitStr[1];
var splitStr = kNew.split('=').map(function(str) { return str.trim(); });
var key = splitStr[0];
var value = splitStr[1];
kNew = key;
d.value = value;
@@ -295,9 +294,9 @@ export function uiRawTagEditor(context) {
tag[kOld] = undefined;
tag[kNew] = d.value;
d.key = kNew; // Maintain DOM identity through the subsequent update.
d.key = kNew; // Maintain DOM identity through the subsequent update.
if (_newRow === kOld) { // see if this row is still a new row
if (_newRow === kOld) { // see if this row is still a new row
_newRow = ((d.value === '' || kNew === '') ? kNew : undefined);
}
+5 -4
View File
@@ -19,7 +19,7 @@ export function uiSettingsCustomBackground(context) {
var modal = uiConfirm(selection).okButton();
modal
.classed('settings-custom-background', true);
.classed('settings-modal settings-custom-background', true);
modal.select('.modal-section.header')
.append('h3')
@@ -30,11 +30,12 @@ export function uiSettingsCustomBackground(context) {
textSection
.append('pre')
.attr('class', 'instructions')
.attr('class', 'instructions-template')
.text(t('settings.custom_background.instructions', { example: example }));
textSection
.append('textarea')
.attr('class', 'field-template')
.attr('placeholder', t('settings.custom_background.template.placeholder'))
.call(utilNoAuto)
.property('value', _currSettings.template);
@@ -66,7 +67,7 @@ export function uiSettingsCustomBackground(context) {
// restore the original template
function clickCancel() {
textSection.select('textarea').property('value', _origSettings.template);
textSection.select('.field-template').property('value', _origSettings.template);
context.storage('background-custom-template', _origSettings.template);
this.blur();
modal.close();
@@ -74,7 +75,7 @@ export function uiSettingsCustomBackground(context) {
// accept the current template
function clickSave() {
_currSettings.template = textSection.select('textarea').property('value');
_currSettings.template = textSection.select('.field-template').property('value');
context.storage('background-custom-template', _currSettings.template);
this.blur();
modal.close();
+121
View File
@@ -0,0 +1,121 @@
import _cloneDeep from 'lodash-es/cloneDeep';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { event as d3_event } from 'd3-selection';
import { t } from '../../util/locale';
import { uiConfirm } from '../confirm';
import { utilNoAuto, utilRebind } from '../../util';
export function uiSettingsCustomData(context) {
var dispatch = d3_dispatch('change');
function render(selection) {
var dataLayer = context.layers().layer('data');
var _origSettings = {
fileList: (dataLayer && dataLayer.fileList()) || null,
url: context.storage('settings-custom-data-url')
};
var _currSettings = _cloneDeep(_origSettings);
// var example = 'https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png';
var modal = uiConfirm(selection).okButton();
modal
.classed('settings-modal settings-custom-data', true);
modal.select('.modal-section.header')
.append('h3')
.text(t('settings.custom_data.header'));
var textSection = modal.select('.modal-section.message-text');
textSection
.append('pre')
.attr('class', 'instructions-file')
.text(t('settings.custom_data.file.instructions'));
textSection
.append('input')
.attr('class', 'field-file')
.attr('type', 'file')
.property('files', _currSettings.fileList) // works for all except IE11
.on('change', function() {
var files = d3_event.target.files;
if (files && files.length) {
_currSettings.url = '';
textSection.select('.field-url').property('value', '');
_currSettings.fileList = files;
} else {
_currSettings.fileList = null;
}
});
textSection
.append('h4')
.text(t('settings.custom_data.or'));
textSection
.append('pre')
.attr('class', 'instructions-url')
.text(t('settings.custom_data.url.instructions'));
textSection
.append('textarea')
.attr('class', 'field-url')
.attr('placeholder', t('settings.custom_data.url.placeholder'))
.call(utilNoAuto)
.property('value', _currSettings.url);
// insert a cancel button, and adjust the button widths
var buttonSection = modal.select('.modal-section.buttons');
buttonSection
.insert('button', '.ok-button')
.attr('class', 'button col3 cancel-button secondary-action')
.text(t('confirm.cancel'));
buttonSection.select('.cancel-button')
.on('click.cancel', clickCancel);
buttonSection.select('.ok-button')
.classed('col3', true)
.classed('col4', false)
.attr('disabled', isSaveDisabled)
.on('click.save', clickSave);
function isSaveDisabled() {
return null;
}
// restore the original url
function clickCancel() {
textSection.select('.field-url').property('value', _origSettings.url);
context.storage('settings-custom-data-url', _origSettings.url);
this.blur();
modal.close();
}
// accept the current url
function clickSave() {
_currSettings.url = textSection.select('.field-url').property('value').trim();
// one or the other but not both
if (_currSettings.url) { _currSettings.fileList = null; }
if (_currSettings.fileList) { _currSettings.url = ''; }
context.storage('settings-custom-data-url', _currSettings.url);
this.blur();
modal.close();
dispatch.call('change', this, _currSettings);
}
}
return utilRebind(render, dispatch, 'on');
}
+1
View File
@@ -1 +1,2 @@
export { uiSettingsCustomBackground } from './custom_background';
export { uiSettingsCustomData } from './custom_data';
+34 -21
View File
@@ -2,18 +2,26 @@ import _throttle from 'lodash-es/throttle';
import { selectAll as d3_selectAll } from 'd3-selection';
import { osmNote } from '../osm';
import { uiFeatureList } from './feature_list';
import { uiInspector } from './inspector';
import { uiNoteEditor } from './note_editor';
import {
osmEntity,
osmNote
} from '../osm';
import {
uiDataEditor,
uiFeatureList,
uiInspector,
uiNoteEditor
} from './index';
export function uiSidebar(context) {
var inspector = uiInspector(context);
var dataEditor = uiDataEditor(context);
var noteEditor = uiNoteEditor(context);
var _current;
var _wasData = false;
var _wasNote = false;
// var layer = d3_select(null);
function sidebar(selection) {
@@ -22,26 +30,31 @@ export function uiSidebar(context) {
.attr('class', 'feature-list-pane')
.call(uiFeatureList(context));
var inspectorWrap = selection
.append('div')
.attr('class', 'inspector-hidden inspector-wrap fr');
function hover(what) {
if ((what instanceof osmNote) && (context.mode().id !== 'drag-note')) {
// TODO: figure out why `what` isn't an updated note. Won't hover since .loc doesn't match
_wasNote = true;
var notes = d3_selectAll('.note');
notes
.classed('hover', function(d) { return d === what; });
sidebar.show(noteEditor.note(what));
function hover(datum) {
if (datum && datum.__featurehash__) { // hovering on data
_wasData = true;
sidebar
.show(dataEditor.datum(datum));
selection.selectAll('.sidebar-component')
.classed('inspector-hover', true);
} else if (!_current && context.hasEntity(what)) {
} else if (datum instanceof osmNote) {
if (context.mode().id === 'drag-note') return;
_wasNote = true;
sidebar
.show(noteEditor.note(datum));
selection.selectAll('.sidebar-component')
.classed('inspector-hover', true);
} else if (!_current && (datum instanceof osmEntity)) {
featureListWrap
.classed('inspector-hidden', true);
@@ -49,10 +62,10 @@ export function uiSidebar(context) {
.classed('inspector-hidden', false)
.classed('inspector-hover', true);
if (inspector.entityID() !== what || inspector.state() !== 'hover') {
if (inspector.entityID() !== datum.id || inspector.state() !== 'hover') {
inspector
.state('hover')
.entityID(what);
.entityID(datum.id);
inspectorWrap
.call(inspector);
@@ -66,10 +79,10 @@ export function uiSidebar(context) {
inspector
.state('hide');
} else if (_wasNote) {
} else if (_wasData || _wasNote) {
_wasNote = false;
d3_selectAll('.note')
.classed('hover', false);
_wasData = false;
d3_selectAll('.note').classed('hover', false);
sidebar.hide();
}
}
+2 -1
View File
@@ -12,6 +12,7 @@ export { utilFunctor } from './util';
export { utilGetAllNodes } from './util';
export { utilGetPrototypeOf } from './util';
export { utilGetSetValue } from './get_set_value';
export { utilHashcode } from './util';
export { utilIdleWorker } from './idle_worker';
export { utilNoAuto } from './util';
export { utilPrefixCSSProperty } from './util';
@@ -25,4 +26,4 @@ export { utilSuggestNames } from './suggest_names';
export { utilTagText } from './util';
export { utilTiler } from './tiler';
export { utilTriggerEvent } from './trigger_event';
export { utilWrap } from './util';
export { utilWrap } from './util';
+16
View File
@@ -266,3 +266,19 @@ export function utilNoAuto(selection) {
.attr('autocapitalize', 'off')
.attr('spellcheck', isText ? 'true' : 'false');
}
// https://stackoverflow.com/questions/194846/is-there-any-kind-of-hash-code-function-in-javascript
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
export function utilHashcode(str) {
var hash = 0;
if (str.length === 0) {
return hash;
}
for (var i = 0; i < str.length; i++) {
var char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
+3
View File
@@ -34,9 +34,12 @@
"@mapbox/sexagesimal": "1.1.0",
"@mapbox/togeojson": "0.16.0",
"@mapbox/vector-tile": "^1.3.1",
"@turf/bbox-clip": "^6.0.0",
"diacritics": "1.3.0",
"fast-json-stable-stringify": "2.0.0",
"lodash-es": "4.17.10",
"marked": "0.5.0",
"martinez-polygon-clipping": "0.5.0",
"node-diff3": "1.0.0",
"osm-auth": "1.0.2",
"pannellum": "2.4.1",
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<gpx version="1.1" creator="GDAL 2.2.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ogr="http://osgeo.org/gdal" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<metadata><bounds minlat="40.150275473401365" minlon="-74.389286041259766" maxlat="40.150275473401365" maxlon="-74.389286041259766"/></metadata>
<wpt lat="40.150275473401365" lon="-74.389286041259766">
<name>New Jersey</name>
<extensions>
<ogr:abbr>N.J.</ogr:abbr>
<ogr:area>19717.8</ogr:area>
<ogr:name_en>New Jersey</ogr:name_en>
<ogr:osm_id>316973311</ogr:osm_id>
</extensions>
</wpt>
</gpx>
-23
View File
@@ -1,23 +0,0 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"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
},
"id": 316973311
}
]
}
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document id="root_doc">
<Schema name="gpxtest" id="gpxtest">
<SimpleField name="abbr" type="string"></SimpleField>
<SimpleField name="area" type="float"></SimpleField>
<SimpleField name="name_en" type="string"></SimpleField>
<SimpleField name="osm_id" type="int"></SimpleField>
</Schema>
<Folder><name>gpxtest</name>
<Placemark>
<name>New Jersey</name>
<ExtendedData><SchemaData schemaUrl="#gpxtest">
<SimpleData name="abbr">N.J.</SimpleData>
<SimpleData name="area">19717.8</SimpleData>
<SimpleData name="name_en">New Jersey</SimpleData>
<SimpleData name="osm_id">316973311</SimpleData>
</SchemaData></ExtendedData>
<Point><coordinates>-74.3892860412598,40.1502754734014</coordinates></Point>
</Placemark>
</Folder>
</Document></kml>
Binary file not shown.
+2 -3
View File
@@ -115,12 +115,11 @@
<script src='spec/services/taginfo.js'></script>
<script src='spec/svg/areas.js'></script>
<script src='spec/svg/gpx.js'></script>
<script src='spec/svg/data.js'></script>
<script src='spec/svg/icon.js'></script>
<script src='spec/svg/layers.js'></script>
<script src='spec/svg/lines.js'></script>
<script src='spec/svg/midpoints.js'></script>
<script src='spec/svg/mvt.js'></script>
<script src='spec/svg/osm.js'></script>
<script src='spec/svg/points.js'></script>
<script src='spec/svg/svg.js'></script>
@@ -149,4 +148,4 @@
</script>
</body>
</html>
</html>
+204
View File
@@ -0,0 +1,204 @@
describe('iD.svgData', 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 geojson =
'{' +
' "type": "FeatureCollection",' +
' "features": [' +
' {' +
' "type": "Feature",' +
' "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' +
' },' +
' "id": 316973311' +
' }' +
' ]' +
'}';
var gj = JSON.parse(geojson);
var gpx =
'<?xml version="1.0"?>' +
'<gpx version="1.1" creator="GDAL 2.2.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ogr="http://osgeo.org/gdal" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">' +
'<metadata><bounds minlat="40.150275473401365" minlon="-74.389286041259766" maxlat="40.150275473401365" maxlon="-74.389286041259766"/></metadata>' +
'<wpt lat="40.150275473401365" lon="-74.389286041259766">' +
' <name>New Jersey</name>' +
' <extensions>' +
' <ogr:abbr>N.J.</ogr:abbr>' +
' <ogr:area>19717.8</ogr:area>' +
' <ogr:name_en>New Jersey</ogr:name_en>' +
' <ogr:osm_id>316973311</ogr:osm_id>' +
' </extensions>' +
'</wpt>' +
'</gpx>';
var kml =
'<?xml version="1.0" encoding="utf-8" ?>' +
'<kml xmlns="http://www.opengis.net/kml/2.2">' +
'<Document id="root_doc">' +
'<Schema name="gpxtest" id="gpxtest">' +
' <SimpleField name="abbr" type="string"></SimpleField>' +
' <SimpleField name="area" type="float"></SimpleField>' +
' <SimpleField name="name_en" type="string"></SimpleField>' +
' <SimpleField name="osm_id" type="int"></SimpleField>' +
'</Schema>' +
'<Folder><name>gpxtest</name>' +
' <Placemark>' +
' <name>New Jersey</name>' +
' <ExtendedData><SchemaData schemaUrl="#gpxtest">' +
' <SimpleData name="abbr">N.J.</SimpleData>' +
' <SimpleData name="area">19717.8</SimpleData>' +
' <SimpleData name="name_en">New Jersey</SimpleData>' +
' <SimpleData name="osm_id">316973311</SimpleData>' +
' </SchemaData></ExtendedData>' +
' <Point><coordinates>-74.3892860412598,40.1502754734014</coordinates></Point>' +
' </Placemark>' +
'</Folder>' +
'</Document>' +
'</kml>';
// this is because PhantomJS hasn't implemented a proper File constructor
function makeFile(contents, fileName, mimeType) {
var blob = new Blob([contents], { type: mimeType });
blob.lastModifiedDate = new Date();
blob.name = fileName;
return blob;
}
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-mapdata', function () {
var render = iD.svgData(projection, context, dispatch).geojson(gj);
surface.call(render);
var layers = surface.selectAll('g.layer-mapdata').nodes();
expect(layers.length).to.eql(1);
});
it('draws geojson', function () {
var render = iD.svgData(projection, context, dispatch).geojson(gj);
surface.call(render);
var path;
path = surface.selectAll('path.shadow');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
path = surface.selectAll('path.stroke');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
});
describe('#fileList', function() {
it('handles gpx files', function (done) {
var files = [ makeFile(gpx, 'test.gpx', 'application/gpx+xml') ];
var render = iD.svgData(projection, context, dispatch);
var spy = sinon.spy();
dispatch.on('change', spy);
render.fileList(files);
window.setTimeout(function() {
expect(spy).to.have.been.calledOnce;
surface.call(render);
var path;
path = surface.selectAll('path.shadow');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
path = surface.selectAll('path.stroke');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
done();
}, 200);
});
it('handles kml files', function (done) {
var files = [ makeFile(kml, 'test.kml', 'application/vnd.google-earth.kml+xml') ];
var render = iD.svgData(projection, context, dispatch);
var spy = sinon.spy();
dispatch.on('change', spy);
render.fileList(files);
window.setTimeout(function() {
expect(spy).to.have.been.calledOnce;
surface.call(render);
var path;
path = surface.selectAll('path.shadow');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
path = surface.selectAll('path.stroke');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
done();
}, 200);
});
it('handles geojson files', function (done) {
var files = [ makeFile(geojson, 'test.geojson', 'application/vnd.geo+json') ];
var render = iD.svgData(projection, context, dispatch);
var spy = sinon.spy();
dispatch.on('change', spy);
render.fileList(files);
window.setTimeout(function() {
expect(spy).to.have.been.calledOnce;
surface.call(render);
var path;
path = surface.selectAll('path.shadow');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
path = surface.selectAll('path.stroke');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
done();
}, 200);
});
});
describe('#showLabels', function() {
it('shows labels by default', function () {
var render = iD.svgData(projection, context, dispatch).geojson(gj);
surface.call(render);
var label = surface.selectAll('text.label');
expect(label.nodes().length).to.eql(1);
expect(label.text()).to.eql('New Jersey');
var halo = surface.selectAll('text.label-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.svgData(projection, context, dispatch).geojson(gj).showLabels(false);
surface.call(render);
expect(surface.selectAll('text.label').empty()).to.be.ok;
expect(surface.selectAll('text.label-halo').empty()).to.be.ok;
});
});
});
-118
View File
@@ -1,118 +0,0 @@
describe('iD.svgGpx', 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-gpx', function () {
var render = iD.svgGpx(projection, context, dispatch);
surface.call(render);
var layers = surface.selectAll('g.layer-gpx').nodes();
expect(layers.length).to.eql(1);
});
it('draws geojson', function () {
var render = iD.svgGpx(projection, context, dispatch).geojson(gj);
surface.call(render);
var path = surface.selectAll('path.gpx');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
});
describe('#files', function() {
it('handles gpx files', function () {
var files = '../../data/gpxtest.gpx';
var render = iD.svgGpx(projection, context, dispatch).files(files);
surface.call(render);
var path = surface.selectAll('path.gpx');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
});
it('handles geojson files', function () {
var files = '../../data/gpxtest.json';
var render = iD.svgGpx(projection, context, dispatch).files(files);
surface.call(render);
var path = surface.selectAll('path.gpx');
expect(path.nodes().length).to.eql(1);
expect(path.attr('d')).to.match(/^M.*z$/);
});
it('handles kml files', function () {
var files = '../../data/gpxtest.kml';
var render = iD.svgGpx(projection, context, dispatch).files(files);
surface.call(render);
var path = surface.selectAll('path.gpx');
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.svgGpx(projection, context, dispatch).geojson(gj);
surface.call(render);
var label = surface.selectAll('text.gpxlabel');
expect(label.nodes().length).to.eql(1);
expect(label.text()).to.eql('New Jersey');
var halo = surface.selectAll('text.gpxlabel-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.svgGpx(projection, context, dispatch).geojson(gj).showLabels(false);
surface.call(render);
expect(surface.selectAll('text.gpxlabel').empty()).to.be.ok;
expect(surface.selectAll('text.gpxlabel-halo').empty()).to.be.ok;
});
});
});
+7 -8
View File
@@ -26,16 +26,15 @@ describe('iD.svgLayers', function () {
it('creates default data layers', function () {
container.call(iD.svgLayers(projection, context));
var nodes = container.selectAll('svg .data-layer').nodes();
expect(nodes.length).to.eql(9);
expect(nodes.length).to.eql(8);
expect(d3.select(nodes[0]).classed('data-layer-osm')).to.be.true;
expect(d3.select(nodes[1]).classed('data-layer-notes')).to.be.true;
expect(d3.select(nodes[2]).classed('data-layer-gpx')).to.be.true;
expect(d3.select(nodes[3]).classed('data-layer-mvt')).to.be.true;
expect(d3.select(nodes[4]).classed('data-layer-streetside')).to.be.true;
expect(d3.select(nodes[5]).classed('data-layer-mapillary-images')).to.be.true;
expect(d3.select(nodes[6]).classed('data-layer-mapillary-signs')).to.be.true;
expect(d3.select(nodes[7]).classed('data-layer-openstreetcam-images')).to.be.true;
expect(d3.select(nodes[8]).classed('data-layer-debug')).to.be.true;
expect(d3.select(nodes[2]).classed('data-layer-data')).to.be.true;
expect(d3.select(nodes[3]).classed('data-layer-streetside')).to.be.true;
expect(d3.select(nodes[4]).classed('data-layer-mapillary-images')).to.be.true;
expect(d3.select(nodes[5]).classed('data-layer-mapillary-signs')).to.be.true;
expect(d3.select(nodes[6]).classed('data-layer-openstreetcam-images')).to.be.true;
expect(d3.select(nodes[7]).classed('data-layer-debug')).to.be.true;
});
});
-97
View File
@@ -1,97 +0,0 @@
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;
});
});
});