mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-12 12:41:58 +02:00
+1
-1
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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)"
|
||||
|
||||
Vendored
+18
-12
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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,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';
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 +1,2 @@
|
||||
export { uiSettingsCustomBackground } from './custom_background';
|
||||
export { uiSettingsCustomData } from './custom_data';
|
||||
|
||||
+34
-21
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user