diff --git a/.travis.yml b/.travis.yml index b74ebb9f4..2463156b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,4 @@ node_js: - "8" sudo: required after_success: - - if [[ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "master" ]]; then exit 0; fi - - pip install virtualenv - - virtualenv ~/env - - source ~/env/bin/activate - - pip install transifex-client - - sudo echo $'[https://www.transifex.com]\nhostname = https://www.transifex.com\nusername = '"$TRANSIFEX_USER"$'\npassword = '"$TRANSIFEX_PASSWORD"$'\n' > ~/.transifexrc - - tx push -s + - ./scripts/txpush.sh diff --git a/css/80_app.css b/css/80_app.css index e2e267712..190766206 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2484,13 +2484,16 @@ img.tile { } .tile-label-debug { + font-size: 10px; background: rgba(0, 0, 0, 0.7); color: #fff; position: absolute; text-align: center; - width: 128px; + padding: 5px; border-radius: 3px; z-index: 2; + margin-left: -50px; + margin-top: -20px; transform-origin:0 0; -ms-transform-origin:0 0; @@ -2505,7 +2508,7 @@ img.tile { } img.tile-debug { - border: 1px solid red; + outline: 1px solid red; } img.tile-loaded { @@ -2644,38 +2647,96 @@ img.tile-removing { /* Info Box ------------------------------------------------------- */ -.infobox { +.info-panels { + display: flex; + flex-flow: row-reverse wrap-reverse; position: absolute; z-index: 1; right: 0; bottom: 30px; - width: 240px; - border-radius: 4px 0 0 0; - border-bottom: 1px solid black; -ms-user-select: element; } -.infobox .infobox-heading { - display: block; - border-radius: 4px 0 0 0; - padding: 5px 10px; - height: 30px; +.info-panels h1, +.info-panels h2, +.info-panels h3, +.info-panels h4, +.info-panels h5 { + display: inline-block; + margin-bottom: 0; } -.infobox ul { +.info-panels h1, +.info-panels h2, +.info-panels h3 { + color: #ff8; +} + +.panel-container { + flex: 0 0 auto; + margin: 2px 0 0 2px; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.75); + padding-bottom: 10px; + width: 250px; +} + +.panel-container .panel-title { + border-radius: 4px 4px 0 0; +} + +.panel-title { padding: 5px 10px; } -.infobox .button { - position: absolute; +.panel-title button.close { + float: right; + height: 20px; + background: none; + color: #ddd; +} + +.panel-title button.close:hover { + color: #fff; +} + +.panel-title button.close .icon { + height: 20px; + width: 16px; +} + +.panel-content { + padding: 5px 10px; + position: relative; +} + +.panel-content .button { + display: inline-block; background: #7092ff; border-radius: 2px; padding: 0 4px; + margin-top: 10px; color: white; - top: 40px; - right: 10px; } +.panel-content-history .links a { + margin-left: 10px; +} +[dir='rtl'] .panel-content-history .links a { + margin-left: auto; + margin-right: 10px; +} + +.panel-content-history .view-history-on-osm { + display: block; + margin-top: 10px; +} + +.panel-content-location .location-info { + margin-top: 10px; +} + + /* About Section ------------------------------------------------------- */ @@ -3111,6 +3172,7 @@ img.tile-removing { .modal-shortcuts .modal-section:last-child { padding-top: 10px; + min-height: 275px; } .modal-shortcuts .tabs-bar { @@ -3145,6 +3207,11 @@ img.tile-removing { width: 50%; } +.modal-shortcuts .shortcut-tab-tools .shortcut-column { + flex: 1 1 100%; + width: 100%; +} + .modal-shortcuts td { padding-bottom: 5px; } diff --git a/data/core.yaml b/data/core.yaml index b26bb755b..4d2ce97f7 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -272,19 +272,44 @@ en: contributors: list: "Edits by {users}" truncated_list: "Edits by {users} and {count} others" - infobox: + info_panels: key: I - selected: "{n} selected" - geometry: Geometry - closed: closed - center: Center - perimeter: Perimeter - length: Length - area: Area - centroid: Centroid - location: Location - metric: Metric - imperial: Imperial + background: + key: B + title: Background + zoom: Zoom + vintage: Vintage + unknown: Unknown + show_tiles: Show Tiles + hide_tiles: Hide Tiles + history: + key: H + title: History + selected: "{n} selected" + version: Version + last_edit: Last Edit + edited_by: Edited By + changeset: Changeset + unknown: Unknown + link_text: History on openstreetmap.org + location: + key: L + title: Location + unknown_location: Unknown Location + measurement: + key: M + title: Measurement + selected: "{n} selected" + geometry: Geometry + closed: closed + center: Center + perimeter: Perimeter + length: Length + area: Area + centroid: Centroid + location: Location + metric: Metric + imperial: Imperial geometry: point: point vertex: vertex @@ -1076,7 +1101,6 @@ en: lasso: "Draw a selection lasso around features" with_selected: title: "With feature selected" - infobox: "Toggle info / measurement box" edit_menu: "Toggle edit menu" vertex_selected: title: "With node selected" @@ -1116,3 +1140,12 @@ en: undo: "Undo last action" redo: "Redo last action" save: "Save changes" + tools: + title: "Tools" + info: + title: "Information" + all: "Toggle all information panels" + background: "Toggle background panel" + history: "Toggle history panel" + location: "Toggle location panel" + measurement: "Toggle measurement panel" diff --git a/data/imagery.json b/data/imagery.json index 37efa3ea6..dddc404ee 100644 --- a/data/imagery.json +++ b/data/imagery.json @@ -5,6 +5,8 @@ "name": "2013 aerial imagery for San Juan County WA", "type": "tms", "template": "http://sjcgis.org/arcgis/rest/services/Basemaps/Aerials_2013_WM/MapServer/tile/{zoom}/{y}/{x}", + "endDate": "2013-06-01T00:00:00.000Z", + "startDate": "2013-05-01T00:00:00.000Z", "scaleExtent": [ 0, 19 @@ -162,6 +164,8 @@ "name": "2016 aerial imagery for San Juan County WA", "type": "tms", "template": "http://sjcgis.org/arcgis/rest/services/Basemaps/Aerials_2016_WM/MapServer/tile/{zoom}/{y}/{x}", + "endDate": "2016-07-01T00:00:00.000Z", + "startDate": "2016-05-01T00:00:00.000Z", "scaleExtent": [ 0, 19 @@ -1631,6 +1635,8 @@ "name": "Basemap geoportail.lu", "type": "tms", "template": "https://{switch:wmts3,wmts4}.geoportail.lu/opendata/wmts/basemap/GLOBAL_WEBMERCATOR_4_V3/{zoom}/{x}/{y}.png", + "endDate": "2010-07-20T00:00:00.000Z", + "startDate": "2013-07-19T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -6528,6 +6534,8 @@ "name": "British Columbia Mosaic", "type": "tms", "template": "http://{switch:a,b,c,d}.imagery.paulnorman.ca/tiles/bc_mosaic/{zoom}/{x}/{y}.png", + "endDate": "2013-06-01T00:00:00.000Z", + "startDate": "2009-01-01T00:00:00.000Z", "scaleExtent": [ 9, 20 @@ -8645,6 +8653,8 @@ "name": "City of Cape Town 2013 Aerial", "type": "tms", "template": "http://{switch:a,b,c}.coct.aerial.openstreetmap.org.za/layer/za_coct_aerial_2013/{zoom}/{x}/{y}.jpg", + "endDate": "2015-01-01T00:00:00.000Z", + "startDate": "2013-01-01T00:00:00.000Z", "scaleExtent": [ 1, 21 @@ -9450,6 +9460,8 @@ "name": "City of Cape Town 2015 Aerial", "type": "tms", "template": "http://{switch:a,b,c}.coct.aerial.openstreetmap.org.za/layer/za_coct_aerial_2015/{zoom}/{x}/{y}.jpg", + "endDate": "2016-01-01T00:00:00.000Z", + "startDate": "2015-01-01T00:00:00.000Z", "scaleExtent": [ 1, 21 @@ -14343,6 +14355,8 @@ "name": "FÖMI orthophoto 2000", "type": "tms", "template": "http://e.tile.openstreetmap.hu/ortofoto2000/{zoom}/{x}/{y}.jpg", + "endDate": "2000-01-01T00:00:00.000Z", + "startDate": "2000-01-01T00:00:00.000Z", "scaleExtent": [ 0, 17 @@ -16651,6 +16665,8 @@ "name": "FÖMI orthophoto 2005", "type": "tms", "template": "http://e.tile.openstreetmap.hu/ortofoto2005/{zoom}/{x}/{y}.jpg", + "endDate": "2005-01-01T00:00:00.000Z", + "startDate": "2005-01-01T00:00:00.000Z", "scaleExtent": [ 0, 17 @@ -24559,6 +24575,8 @@ "name": "imagico.de OSM images for mapping: Adams Bridge", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R119_N09_20160327T050917&z={zoom}&x={x}&y={-y}", + "endDate": "2016-03-27T00:00:00.000Z", + "startDate": "2016-03-27T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -24597,6 +24615,8 @@ "name": "imagico.de OSM images for mapping: Alaska Range", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC80700162014211LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2014-07-31T00:00:00.000Z", + "startDate": "2014-07-31T00:00:00.000Z", "scaleExtent": [ 0, 12 @@ -24639,6 +24659,8 @@ "name": "imagico.de OSM images for mapping: Bakun Reservoir", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81190582014075LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2014-03-16T00:00:00.000Z", + "startDate": "2014-03-16T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -24681,6 +24703,8 @@ "name": "imagico.de OSM images for mapping: Batam", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81250592016107LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2016-01-01T00:00:00.000Z", + "startDate": "2014-01-01T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -24719,6 +24743,8 @@ "name": "imagico.de OSM images for mapping: Bouvet Island", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81800982013291LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2013-10-18T00:00:00.000Z", + "startDate": "2013-10-18T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -24758,6 +24784,8 @@ "name": "imagico.de OSM images for mapping: Cental Alps in late September 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R065_N47_20160929T102022&z={zoom}&x={x}&y={-y}", + "endDate": "2016-09-29T00:00:00.000Z", + "startDate": "2016-09-29T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -24808,6 +24836,8 @@ "name": "imagico.de OSM images for mapping: Clerke Rocks", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC82050982015344LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2015-12-10T00:00:00.000Z", + "startDate": "2015-12-10T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -24850,6 +24880,8 @@ "name": "imagico.de OSM images for mapping: Coropuna", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=EO1A0040712016264110KF&z={zoom}&x={x}&y={-y}", + "endDate": "2016-09-21T00:00:00.000Z", + "startDate": "2016-09-21T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -24896,6 +24928,8 @@ "name": "imagico.de OSM images for mapping: Cotonou", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R022_N06_20151221T103009&z={zoom}&x={x}&y={-y}", + "endDate": "2015-12-21T00:00:00.000Z", + "startDate": "2015-12-21T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -24938,6 +24972,8 @@ "name": "imagico.de OSM images for mapping: Darwin and Wolf islands, Galapagos", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R040_N01_20160311T164128&z={zoom}&x={x}&y={-y}", + "endDate": "2016-03-11T00:00:00.000Z", + "startDate": "2016-03-11T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -24976,6 +25012,8 @@ "name": "imagico.de OSM images for mapping: Eastern Devon Island coast", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC80360072014245LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2014-09-02T00:00:00.000Z", + "startDate": "2014-09-02T00:00:00.000Z", "scaleExtent": [ 0, 11 @@ -25014,6 +25052,8 @@ "name": "imagico.de OSM images for mapping: Eastern Iceland", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC82160152013239LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2013-08-27T00:00:00.000Z", + "startDate": "2013-08-27T00:00:00.000Z", "scaleExtent": [ 0, 12 @@ -25052,6 +25092,8 @@ "name": "imagico.de OSM images for mapping: El Altar", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=AST_L1T_00302052007154424_20150518041444_91492&z={zoom}&x={x}&y={-y}", + "endDate": "2012-02-05T00:00:00.000Z", + "startDate": "2012-02-05T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -25090,6 +25132,8 @@ "name": "imagico.de OSM images for mapping: Elephant Island/Clarence Island", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R009_S61_20160109&z={zoom}&x={x}&y={-y}", + "endDate": "2016-01-09T00:00:00.000Z", + "startDate": "2016-01-09T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -25132,6 +25176,8 @@ "name": "imagico.de OSM images for mapping: Enderby Land and Kemp Coast", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=enderby&z={zoom}&x={x}&y={-y}", + "endDate": "2017-03-27T00:00:00.000Z", + "startDate": "2017-01-25T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -25182,6 +25228,8 @@ "name": "imagico.de OSM images for mapping: Fogo, Cape Verde", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC82100502015347LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2015-12-13T00:00:00.000Z", + "startDate": "2015-12-13T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -25220,6 +25268,8 @@ "name": "imagico.de OSM images for mapping: Greenland mosaic", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=greenland&z={zoom}&x={x}&y={-y}", + "endDate": "2015-01-01T00:00:00.000Z", + "startDate": "2013-01-01T00:00:00.000Z", "scaleExtent": [ 0, 12 @@ -25870,6 +25920,8 @@ "name": "imagico.de OSM images for mapping: Heard Island coast", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R047_S54_20160411T044330&z={zoom}&x={x}&y={-y}", + "endDate": "2016-04-12T00:00:00.000Z", + "startDate": "2016-04-12T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -25912,6 +25964,8 @@ "name": "imagico.de OSM images for mapping: Isla Londonderry", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC82280982013259LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2013-09-16T00:00:00.000Z", + "startDate": "2013-09-16T00:00:00.000Z", "scaleExtent": [ 0, 12 @@ -25962,6 +26016,8 @@ "name": "imagico.de OSM images for mapping: Kerch Strait", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R021_N44_20160807T083013&z={zoom}&x={x}&y={-y}", + "endDate": "2016-08-07T00:00:00.000Z", + "startDate": "2016-08-07T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -26000,6 +26056,8 @@ "name": "imagico.de OSM images for mapping: Landsat off-nadir July 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=ls_polar2&z={zoom}&x={x}&y={-y}", + "endDate": "2016-07-17T00:00:00.000Z", + "startDate": "2016-07-17T00:00:00.000Z", "scaleExtent": [ 0, 10 @@ -26050,6 +26108,8 @@ "name": "imagico.de OSM images for mapping: Leskov Island ASTER", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=AST_L1T_00311162013112731_20150618142416_109190&z={zoom}&x={x}&y={-y}", + "endDate": "2013-11-16T00:00:00.000Z", + "startDate": "2013-11-16T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -26088,6 +26148,8 @@ "name": "imagico.de OSM images for mapping: Leskov Island Landsat", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81991002015286LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2015-10-13T00:00:00.000Z", + "startDate": "2015-10-13T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -26130,6 +26192,8 @@ "name": "imagico.de OSM images for mapping: May 2013 off-nadir Landsat", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=ls_polar&z={zoom}&x={x}&y={-y}", + "endDate": "2013-05-17T00:00:00.000Z", + "startDate": "2013-05-17T00:00:00.000Z", "scaleExtent": [ 0, 10 @@ -26200,6 +26264,8 @@ "name": "imagico.de OSM images for mapping: Mount Kenya 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R092_S02_20160613T075613&z={zoom}&x={x}&y={-y}", + "endDate": "2016-06-13T00:00:00.000Z", + "startDate": "2016-06-13T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -26238,6 +26304,8 @@ "name": "imagico.de OSM images for mapping: Mount Kilimanjaro 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R092_S05_20160802T075556&z={zoom}&x={x}&y={-y}", + "endDate": "2016-08-02T00:00:00.000Z", + "startDate": "2016-08-02T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -26276,6 +26344,8 @@ "name": "imagico.de OSM images for mapping: New Ireland", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC80940622015159LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2015-06-08T00:00:00.000Z", + "startDate": "2015-06-08T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -26314,6 +26384,8 @@ "name": "imagico.de OSM images for mapping: North Sea Coast 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=northsea_s2_2016&z={zoom}&x={x}&y={-y}", + "endDate": "2016-09-25T00:00:00.000Z", + "startDate": "2016-09-25T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -26360,6 +26432,8 @@ "name": "imagico.de OSM images for mapping: Northern and Polar Ural mountains August 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=ural_s2_2016&z={zoom}&x={x}&y={-y}", + "endDate": "2016-08-12T00:00:00.000Z", + "startDate": "2016-08-12T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -26406,6 +26480,8 @@ "name": "imagico.de OSM images for mapping: Northern Ellesmere Island", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=nellesmere_ast&z={zoom}&x={x}&y={-y}", + "endDate": "2012-07-09T00:00:00.000Z", + "startDate": "2012-07-09T00:00:00.000Z", "scaleExtent": [ 0, 10 @@ -26452,6 +26528,8 @@ "name": "imagico.de OSM images for mapping: Northern Ellesmere Island July 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=nellesmere_ast_2016&z={zoom}&x={x}&y={-y}", + "endDate": "2012-07-15T00:00:00.000Z", + "startDate": "2012-07-08T00:00:00.000Z", "scaleExtent": [ 0, 10 @@ -26502,6 +26580,8 @@ "name": "imagico.de OSM images for mapping: Northern German west coast tidalflats", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81960222015233LGN00vis&z={zoom}&x={x}&y={-y}", + "endDate": "2015-08-21T00:00:00.000Z", + "startDate": "2015-08-21T00:00:00.000Z", "scaleExtent": [ 0, 12 @@ -26544,6 +26624,8 @@ "name": "imagico.de OSM images for mapping: Northern German west coast tidalflats (infrared)", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81960222015233LGN00ir&z={zoom}&x={x}&y={-y}", + "endDate": "2015-08-21T00:00:00.000Z", + "startDate": "2015-08-21T00:00:00.000Z", "scaleExtent": [ 0, 12 @@ -26586,6 +26668,8 @@ "name": "imagico.de OSM images for mapping: Northern Greenland ASTER", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=ngreenland_ast&z={zoom}&x={x}&y={-y}", + "endDate": "2012-08-13T00:00:00.000Z", + "startDate": "2005-06-21T00:00:00.000Z", "scaleExtent": [ 0, 10 @@ -26644,6 +26728,8 @@ "name": "imagico.de OSM images for mapping: Northwest Heard Island", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=EO1A1350972013086110KF&z={zoom}&x={x}&y={-y}", + "endDate": "2013-03-13T00:00:00.000Z", + "startDate": "2013-03-13T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -26690,6 +26776,8 @@ "name": "imagico.de OSM images for mapping: Panama Canal", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R111_N09_20160604T154554&z={zoom}&x={x}&y={-y}", + "endDate": "2016-06-07T00:00:00.000Z", + "startDate": "2016-06-07T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -26728,6 +26816,8 @@ "name": "imagico.de OSM images for mapping: Panama Canal - Pacific side", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=EO1A0120532016364110KF&z={zoom}&x={x}&y={-y}", + "endDate": "2016-12-30T00:00:00.000Z", + "startDate": "2016-12-30T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -26774,6 +26864,8 @@ "name": "imagico.de OSM images for mapping: Pechora Sea Coast", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R078_N68_20160930T081002&z={zoom}&x={x}&y={-y}", + "endDate": "2016-09-30T00:00:00.000Z", + "startDate": "2016-09-30T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -26832,6 +26924,8 @@ "name": "imagico.de OSM images for mapping: Pensacola Mountains", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81511242016033LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2016-02-02T00:00:00.000Z", + "startDate": "2016-02-02T00:00:00.000Z", "scaleExtent": [ 0, 10 @@ -26878,6 +26972,8 @@ "name": "imagico.de OSM images for mapping: Prokletije Mountains", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R136_N41_20150831T093006&z={zoom}&x={x}&y={-y}", + "endDate": "2015-08-31T00:00:00.000Z", + "startDate": "2015-08-31T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -26916,6 +27012,8 @@ "name": "imagico.de OSM images for mapping: Qasigiannguit", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=DMS_1142622_03746_20110415_17533956&z={zoom}&x={x}&y={-y}", + "endDate": "2011-04-15T00:00:00.000Z", + "startDate": "2011-04-15T00:00:00.000Z", "scaleExtent": [ 0, 15 @@ -26954,6 +27052,8 @@ "name": "imagico.de OSM images for mapping: Rann of Kutch", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81510432015030LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2015-01-01T00:00:00.000Z", + "startDate": "2015-01-01T00:00:00.000Z", "scaleExtent": [ 0, 12 @@ -26996,6 +27096,8 @@ "name": "imagico.de OSM images for mapping: Rila and Pirin Mountains", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R093_N41_20150828T092005&z={zoom}&x={x}&y={-y}", + "endDate": "2015-08-28T00:00:00.000Z", + "startDate": "2015-08-28T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -27038,6 +27140,8 @@ "name": "imagico.de OSM images for mapping: Rwenzori Mountains", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81730602015040LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2015-02-09T00:00:00.000Z", + "startDate": "2015-02-09T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27076,6 +27180,8 @@ "name": "imagico.de OSM images for mapping: Rwenzori Mountains 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R078_N01_20160702T082522&z={zoom}&x={x}&y={-y}", + "endDate": "2016-07-02T00:00:00.000Z", + "startDate": "2016-07-02T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -27114,6 +27220,8 @@ "name": "imagico.de OSM images for mapping: Scott Island", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC80611072014036LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2014-02-05T00:00:00.000Z", + "startDate": "2014-02-05T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27152,6 +27260,8 @@ "name": "imagico.de OSM images for mapping: Shag Rocks", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC82100972015347LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2015-12-13T00:00:00.000Z", + "startDate": "2015-12-13T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27190,6 +27300,8 @@ "name": "imagico.de OSM images for mapping: Southeastern Sulawesi", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81130622013270LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2013-09-27T00:00:00.000Z", + "startDate": "2013-09-27T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27236,6 +27348,8 @@ "name": "imagico.de OSM images for mapping: Southern Transantarctic Mountains", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC80281222016035LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2016-02-04T00:00:00.000Z", + "startDate": "2016-02-04T00:00:00.000Z", "scaleExtent": [ 0, 10 @@ -27294,6 +27408,8 @@ "name": "imagico.de OSM images for mapping: Svalbard mosaic", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=s2sval&z={zoom}&x={x}&y={-y}", + "endDate": "2016-01-01T00:00:00.000Z", + "startDate": "2016-01-01T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27404,6 +27520,8 @@ "name": "imagico.de OSM images for mapping: Thule Air Base", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=DMS_1142636_160xx_20110507_1822xxxx&z={zoom}&x={x}&y={-y}", + "endDate": "2011-05-07T00:00:00.000Z", + "startDate": "2011-05-07T00:00:00.000Z", "scaleExtent": [ 0, 15 @@ -27450,6 +27568,8 @@ "name": "imagico.de OSM images for mapping: Thule Airbase DMS low altitude overflight September 2015", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=dms_thule2_2015.09.25&z={zoom}&x={x}&y={-y}", + "endDate": "2015-09-25T00:00:00.000Z", + "startDate": "2015-09-25T00:00:00.000Z", "scaleExtent": [ 0, 17 @@ -27504,6 +27624,8 @@ "name": "imagico.de OSM images for mapping: Thule Airbase DMS overflight October 2015", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=dms_thule_2015.10.06&z={zoom}&x={x}&y={-y}", + "endDate": "2015-10-06T00:00:00.000Z", + "startDate": "2015-10-06T00:00:00.000Z", "scaleExtent": [ 0, 16 @@ -27554,6 +27676,8 @@ "name": "imagico.de OSM images for mapping: Thule Airbase DMS overflight September 2015", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=dms_thule_2015.09.25&z={zoom}&x={x}&y={-y}", + "endDate": "2015-09-25T00:00:00.000Z", + "startDate": "2015-09-25T00:00:00.000Z", "scaleExtent": [ 0, 16 @@ -27600,6 +27724,8 @@ "name": "imagico.de OSM images for mapping: Ushakov Island August 2016", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R094_N79_20160812T105622&z={zoom}&x={x}&y={-y}", + "endDate": "2016-08-12T00:00:00.000Z", + "startDate": "2016-08-12T00:00:00.000Z", "scaleExtent": [ 0, 12 @@ -27638,6 +27764,8 @@ "name": "imagico.de OSM images for mapping: Vanatinai", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC80910682014358LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2014-12-24T00:00:00.000Z", + "startDate": "2014-12-24T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27680,6 +27808,8 @@ "name": "imagico.de OSM images for mapping: Volcán Calbuco", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC82330892016031LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2016-01-31T00:00:00.000Z", + "startDate": "2016-01-31T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27722,6 +27852,8 @@ "name": "imagico.de OSM images for mapping: Vostochny Cosmodrome", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R089_N52_20160623T024048&z={zoom}&x={x}&y={-y}", + "endDate": "2016-06-23T00:00:00.000Z", + "startDate": "2016-06-23T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27760,6 +27892,8 @@ "name": "imagico.de OSM images for mapping: Western Karakoram", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=LC81490352013282LGN00&z={zoom}&x={x}&y={-y}", + "endDate": "2013-10-09T00:00:00.000Z", + "startDate": "2013-10-09T00:00:00.000Z", "scaleExtent": [ 0, 13 @@ -27798,6 +27932,8 @@ "name": "imagico.de OSM images for mapping: Willkanuta Mountains and Quelccaya Ice Cap", "type": "tms", "template": "http://imagico.de/map/osmim_tiles.php?layer=S2A_R039_S15_20160510T145731&z={zoom}&x={x}&y={-y}", + "endDate": "2016-05-10T00:00:00.000Z", + "startDate": "2016-05-10T00:00:00.000Z", "scaleExtent": [ 0, 14 @@ -28589,6 +28725,8 @@ "name": "Kanton Aargau 25cm (AGIS 2011)", "type": "tms", "template": "http://tiles.poole.ch/AGIS/OF2011/{zoom}/{x}/{y}.png", + "endDate": "2011-01-01T00:00:00.000Z", + "startDate": "2011-01-01T00:00:00.000Z", "scaleExtent": [ 14, 19 @@ -29240,6 +29378,8 @@ "name": "Kanton Aargau 25cm (AGIS 2014)", "type": "tms", "template": "http://mapproxy.osm.ch:8080/tiles/AGIS2014/EPSG900913/{zoom}/{x}/{y}.png?origin=nw", + "endDate": "2014-01-01T00:00:00.000Z", + "startDate": "2014-01-01T00:00:00.000Z", "scaleExtent": [ 8, 19 @@ -29891,6 +30031,8 @@ "name": "Kanton Aargau 25cm (AGIS 2016)", "type": "tms", "template": "http://mapproxy.osm.ch:8080/tiles/AGIS2016/EPSG900913/{zoom}/{x}/{y}.png?origin=nw", + "endDate": "2016-01-01T00:00:00.000Z", + "startDate": "2016-01-01T00:00:00.000Z", "scaleExtent": [ 8, 19 @@ -32526,6 +32668,8 @@ "name": "Kelowna 2012", "type": "tms", "template": "http://{switch:a,b,c,d}.tile.paulnorman.ca/kelowna2012/{zoom}/{x}/{y}.png", + "endDate": "2012-05-14T00:00:00.000Z", + "startDate": "2012-05-13T00:00:00.000Z", "scaleExtent": [ 9, 20 @@ -33324,6 +33468,8 @@ "name": "Landsat 233055", "type": "tms", "template": "http://{switch:a,b,c,d}.tile.paulnorman.ca/landsat_233055/{zoom}/{x}/{y}.png", + "endDate": "2013-09-03T00:00:00.000Z", + "startDate": "2013-09-03T00:00:00.000Z", "scaleExtent": [ 5, 14 @@ -33359,6 +33505,8 @@ "name": "Latest southwest British Columbia Landsat", "type": "tms", "template": "http://{switch:a,b,c,d}.tile.paulnorman.ca/landsat_047026/{zoom}/{x}/{y}.png", + "endDate": "2013-09-12T00:00:00.000Z", + "startDate": "2013-09-12T00:00:00.000Z", "scaleExtent": [ 5, 13 @@ -34067,6 +34215,8 @@ "name": "Lithuania - NŽT ORT10LT", "type": "tms", "template": "http://ort10lt.openmap.lt/g16/{zoom}/{x}/{y}.jpeg", + "endDate": "2016-01-01T00:00:00.000Z", + "startDate": "2010-01-01T00:00:00.000Z", "scaleExtent": [ 4, 18 @@ -36754,6 +36904,8 @@ "name": "MD Latest 6 Inch Aerial Imagery", "type": "tms", "template": "http://whoots.mapwarper.net/tms/{zoom}/{x}/{y}/MD_SixInchImagery/http://geodata.md.gov/imap/services/Imagery/MD_SixInchImagery/MapServer/WmsServer", + "endDate": "2016-01-01T00:00:00.000Z", + "startDate": "2013-01-01T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -37641,6 +37793,8 @@ "name": "NJ 2015 Aerial Imagery (Infrared)", "type": "tms", "template": "http://whoots.mapwarper.net/tms/{zoom}/{x}/{y}/Infrared2015/http://geodata.state.nj.us/imagerywms/Infrared2015", + "endDate": "2015-05-03T00:00:00.000Z", + "startDate": "2015-03-29T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -38098,6 +38252,8 @@ "name": "NJ 2015 Aerial Imagery (Natural Color)", "type": "tms", "template": "http://whoots.mapwarper.net/tms/{zoom}/{x}/{y}/Natural2015/http://geodata.state.nj.us/imagerywms/Natural2015", + "endDate": "2015-05-03T00:00:00.000Z", + "startDate": "2015-03-29T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -45994,6 +46150,7 @@ "name": "NLSC General Map with Contour line", "type": "tms", "template": "http://wmts.nlsc.gov.tw/wmts/EMAP5_OPENDATA/default/EPSG:3857/{zoom}/{y}/{x}", + "startDate": "2015-01-01T00:00:00.000Z", "scaleExtent": [ 0, 15 @@ -48853,6 +49010,8 @@ "name": "Ortho 2010 geoportail.lu", "type": "tms", "template": "https://{switch:wmts3,wmts4}.geoportail.lu/opendata/wmts/ortho_2010/GLOBAL_WEBMERCATOR_4_V3/{zoom}/{x}/{y}.jpeg", + "endDate": "2010-07-02T00:00:00.000Z", + "startDate": "2010-06-24T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -49774,6 +49933,8 @@ "name": "Ortho 2013 geoportail.lu", "type": "tms", "template": "https://{switch:wmts3,wmts4}.geoportail.lu/opendata/wmts/ortho_2013/GLOBAL_WEBMERCATOR_4_V3/{zoom}/{x}/{y}.jpeg", + "endDate": "2013-07-20T00:00:00.000Z", + "startDate": "2013-07-19T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -50695,6 +50856,8 @@ "name": "Ortho 2016 geoportail.lu", "type": "tms", "template": "https://{switch:wmts3,wmts4}.geoportail.lu/opendata/wmts/ortho_2016/GLOBAL_WEBMERCATOR_4_V3/{zoom}/{x}/{y}.jpeg", + "endDate": "2016-08-16T00:00:00.000Z", + "startDate": "2013-08-30T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -57237,6 +57400,7 @@ "name": "Sóskút, Pusztazámor, Tárnok, Diósd ortophoto 2017", "type": "tms", "template": "http://adam.openstreetmap.hu/mapproxy/tiles/1.0.0/Soskut-Tarnok-Pusztazamor-Diosd/mercator/{zoom}/{x}/{y}.png", + "startDate": "2017-03-01T00:00:00.000Z", "polygon": [ [ [ @@ -64443,6 +64607,8 @@ "name": "Surrey Air Survey", "type": "tms", "template": "http://gravitystorm.dev.openstreetmap.org/surrey/{zoom}/{x}/{y}.png", + "endDate": "2009-01-01T00:00:00.000Z", + "startDate": "2007-01-01T00:00:00.000Z", "scaleExtent": [ 8, 19 @@ -65162,6 +65328,7 @@ "name": "Texas Orthophoto", "type": "tms", "template": "https://txgi.tnris.org/login/path/ecology-fiona-poem-romeo/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=texas&STYLE=&FORMAT=image/png&tileMatrixSet=0to20&tileMatrix=0to20:{zoom}&tileRow={y}&tileCol={x}", + "startDate": "2012-01-01T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -65289,6 +65456,8 @@ "name": "Topographical Map geoportail.lu", "type": "tms", "template": "https://{switch:wmts3,wmts4}.geoportail.lu/opendata/wmts/topo/GLOBAL_WEBMERCATOR_4_V3/{zoom}/{x}/{y}.png", + "endDate": "2010-07-20T00:00:00.000Z", + "startDate": "2013-07-19T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -67258,6 +67427,8 @@ "name": "Tours - Orthophotos 2008-2010", "type": "tms", "template": "http://wms.openstreetmap.fr/tms/1.0.0/tours/{zoom}/{x}/{y}", + "endDate": "2011-01-01T00:00:00.000Z", + "startDate": "2008-01-01T00:00:00.000Z", "scaleExtent": [ 0, 20 @@ -67862,6 +68033,8 @@ "name": "Tours - Orthophotos 2013", "type": "tms", "template": "http://wms.openstreetmap.fr/tms/1.0.0/tours_2013/{zoom}/{x}/{y}", + "endDate": "2013-01-01T00:00:00.000Z", + "startDate": "2013-01-01T00:00:00.000Z", "scaleExtent": [ 0, 22 diff --git a/data/shortcuts.json b/data/shortcuts.json index e2b04c9fa..924d9b868 100644 --- a/data/shortcuts.json +++ b/data/shortcuts.json @@ -93,9 +93,8 @@ "shortcuts": ["Right-click", "shortcuts.key.space"], "text": "shortcuts.browsing.with_selected.edit_menu" }, { - "modifiers": ["⌘"], - "shortcuts": ["infobox.key"], - "text": "shortcuts.browsing.with_selected.infobox" + "shortcuts": [], + "text": "" }, { @@ -166,7 +165,7 @@ "shortcuts": ["Z"], "text": "shortcuts.editing.commands.undo" }, { - "modifiers": ["⌘","⇧"], + "modifiers": ["⌘", "⇧"], "shortcuts": ["Z"], "text": "shortcuts.editing.commands.redo" }, { @@ -221,5 +220,38 @@ ] } ] + }, { + "tab": "tools", + "text": "shortcuts.tools.title", + "columns" : [ + { + "rows": [ + { + "section": "info", + "text": "shortcuts.tools.info.title" + }, { + "modifiers": ["⌘"], + "shortcuts": ["info_panels.key"], + "text": "shortcuts.tools.info.all" + }, { + "modifiers": ["⌘", "⇧"], + "shortcuts": ["info_panels.background.key"], + "text": "shortcuts.tools.info.background" + }, { + "modifiers": ["⌘", "⇧"], + "shortcuts": ["info_panels.history.key"], + "text": "shortcuts.tools.info.history" + }, { + "modifiers": ["⌘", "⇧"], + "shortcuts": ["info_panels.location.key"], + "text": "shortcuts.tools.info.location" + }, { + "modifiers": ["⌘", "⇧"], + "shortcuts": ["info_panels.measurement.key"], + "text": "shortcuts.tools.info.measurement" + } + ] + } + ] } ]} diff --git a/data/update_imagery.js b/data/update_imagery.js index b0ea6ef59..cdbe7d1ba 100644 --- a/data/update_imagery.js +++ b/data/update_imagery.js @@ -41,12 +41,6 @@ sources.concat(whitelist).forEach(function(source) { if (source.type !== 'tms' && source.type !== 'bing') return; if (source.id in blacklist) return; - if (source.end_date) { - var endDate = new Date(source.end_date), - isValid = !isNaN(endDate.getTime()); - if (isValid && endDate <= cutoffDate) return; - } - var im = { id: source.id, name: source.name, @@ -54,6 +48,25 @@ sources.concat(whitelist).forEach(function(source) { template: source.url }; + var startDate, endDate, isValid; + + if (source.end_date) { + endDate = new Date(source.end_date); + isValid = !isNaN(endDate.getTime()); + if (isValid) { + if (endDate <= cutoffDate) return; // too old + im.endDate = endDate; + } + } + + if (source.start_date) { + startDate = new Date(source.start_date); + isValid = !isNaN(startDate.getTime()); + if (isValid) { + im.startDate = startDate; + } + } + var extent = source.extent || {}; if (extent.min_zoom || extent.max_zoom) { im.scaleExtent = [ diff --git a/dist/locales/en.json b/dist/locales/en.json index c07e21955..ffb84a0a7 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -348,19 +348,48 @@ "list": "Edits by {users}", "truncated_list": "Edits by {users} and {count} others" }, - "infobox": { + "info_panels": { "key": "I", - "selected": "{n} selected", - "geometry": "Geometry", - "closed": "closed", - "center": "Center", - "perimeter": "Perimeter", - "length": "Length", - "area": "Area", - "centroid": "Centroid", - "location": "Location", - "metric": "Metric", - "imperial": "Imperial" + "background": { + "key": "B", + "title": "Background", + "zoom": "Zoom", + "vintage": "Vintage", + "unknown": "Unknown", + "show_tiles": "Show Tiles", + "hide_tiles": "Hide Tiles" + }, + "history": { + "key": "H", + "title": "History", + "selected": "{n} selected", + "version": "Version", + "last_edit": "Last Edit", + "edited_by": "Edited By", + "changeset": "Changeset", + "unknown": "Unknown", + "link_text": "History on openstreetmap.org" + }, + "location": { + "key": "L", + "title": "Location", + "unknown_location": "Unknown Location" + }, + "measurement": { + "key": "M", + "title": "Measurement", + "selected": "{n} selected", + "geometry": "Geometry", + "closed": "closed", + "center": "Center", + "perimeter": "Perimeter", + "length": "Length", + "area": "Area", + "centroid": "Centroid", + "location": "Location", + "metric": "Metric", + "imperial": "Imperial" + } }, "geometry": { "point": "point", @@ -950,7 +979,6 @@ }, "with_selected": { "title": "With feature selected", - "infobox": "Toggle info / measurement box", "edit_menu": "Toggle edit menu" }, "vertex_selected": { @@ -996,6 +1024,17 @@ "redo": "Redo last action", "save": "Save changes" } + }, + "tools": { + "title": "Tools", + "info": { + "title": "Information", + "all": "Toggle all information panels", + "background": "Toggle background panel", + "history": "Toggle history panel", + "location": "Toggle location panel", + "measurement": "Toggle measurement panel" + } } }, "presets": { diff --git a/modules/index.js b/modules/index.js index 9277223f4..7b79ecf9f 100644 --- a/modules/index.js +++ b/modules/index.js @@ -12,6 +12,7 @@ export * from './services/index'; export * from './svg/index'; export * from './ui/fields/index'; export * from './ui/intro/index'; +export * from './ui/panels/index'; export * from './ui/index'; export * from './util/index'; export * from './lib/index'; diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 38252f1ff..611086449 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -5,6 +5,25 @@ import { geoExtent, geoPolygonIntersectsPolygon } from '../geo/index'; import { jsonpRequest } from '../util/jsonp_request'; +function localeDateString(s) { + if (!s) return null; + var d = new Date(s); + if (isNaN(d.getTime())) return null; + return d.toLocaleDateString(); +} + +function vintageRange(vintage) { + var s; + if (vintage.start || vintage.end) { + s = (vintage.start || '?'); + if (vintage.start !== vintage.end) { + s += ' - ' + (vintage.end || '?'); + } + } + return s; +} + + export function rendererBackgroundSource(data) { var source = _.clone(data), offset = [0, 0], @@ -106,6 +125,16 @@ export function rendererBackgroundSource(data) { source.copyrightNotices = function() {}; + source.getVintage = function(center, tileCoord, callback) { + var vintage = { + start: localeDateString(source.startDate), + end: localeDateString(source.endDate) + }; + vintage.range = vintageRange(vintage); + callback(null, vintage); + }; + + return source; } @@ -120,6 +149,7 @@ rendererBackgroundSource.Bing = function(data, dispatch) { key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU', // Same as P2 and JOSM url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + key + '&jsonp={callback}', + cache = {}, providers = []; jsonpRequest(url, function(json) { @@ -137,6 +167,7 @@ rendererBackgroundSource.Bing = function(data, dispatch) { dispatch.call('change'); }); + bing.copyrightNotices = function(zoom, extent) { zoom = Math.min(zoom, 21); return providers.filter(function(provider) { @@ -150,8 +181,41 @@ rendererBackgroundSource.Bing = function(data, dispatch) { }).join(', '); }; + + bing.getVintage = function(center, tileCoord, callback) { + var tileId = tileCoord.slice(0, 3).join('/'), + zoom = Math.min(tileCoord[2], 21), + centerPoint = center[1] + ',' + center[0], // lat,lng + url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint + + '?zl=' + zoom + '&key=' + key + '&jsonp={callback}'; + + if (!cache[tileId]) { + cache[tileId] = {}; + } + if (cache[tileId] && cache[tileId].vintage) { + return callback(null, cache[tileId].vintage); + } + + jsonpRequest(url, function(result) { + var err = (!result && 'Unknown Error') || result.errorDetails; + if (err) { + return callback(err); + } else { + var vintage = { + start: localeDateString(result.resourceSets[0].resources[0].vintageStart), + end: localeDateString(result.resourceSets[0].resources[0].vintageEnd) + }; + vintage.range = vintageRange(vintage); + cache[tileId].vintage = vintage; + return callback(null, vintage); + } + }); + }; + + bing.terms_url = 'https://blog.openstreetmap.org/2010/11/30/microsoft-imagery-details'; + return bing; }; @@ -159,18 +223,22 @@ rendererBackgroundSource.Bing = function(data, dispatch) { rendererBackgroundSource.None = function() { var source = rendererBackgroundSource({ id: 'none', template: '' }); + source.name = function() { return t('background.none'); }; + source.imageryUsed = function() { return 'None'; }; + source.area = function() { return -1; // sources in background pane are sorted by area }; + return source; }; @@ -178,17 +246,21 @@ rendererBackgroundSource.None = function() { rendererBackgroundSource.Custom = function(template) { var source = rendererBackgroundSource({ id: 'custom', template: template }); + source.name = function() { return t('background.custom'); }; + source.imageryUsed = function() { return 'Custom (' + template + ')'; }; + source.area = function() { return -2; // sources in background pane are sorted by area }; + return source; }; diff --git a/modules/renderer/tile_layer.js b/modules/renderer/tile_layer.js index 8f17930d9..ffc89a1c5 100644 --- a/modules/renderer/tile_layer.js +++ b/modules/renderer/tile_layer.js @@ -1,6 +1,8 @@ import * as d3 from 'd3'; +import { t } from '../util/locale'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { utilPrefixCSSProperty } from '../util/index'; +import { geoEuclideanDistance } from '../geo'; +import { utilPrefixCSSProperty } from '../util'; import { rendererBackgroundSource } from './background_source.js'; @@ -97,7 +99,7 @@ export function rendererTileLayer(context) { tile().forEach(function(d) { addSource(d); if (d[3] === '') return; - if (typeof d[3] !== 'string') return; // Workaround for chrome crash https://github.com/openstreetmap/iD/issues/2295 + if (typeof d[3] !== 'string') return; // Workaround for #2295 requests.push(d); if (cache[d[3]] === false && lookUp(d)) { requests.push(addSource(lookUp(d))); @@ -118,6 +120,7 @@ export function rendererTileLayer(context) { source.offset()[1] * Math.pow(2, z) ]; + function load(d) { cache[d[3]] = true; d3.select(this) @@ -145,14 +148,36 @@ export function rendererTileLayer(context) { 'scale(' + scale + ',' + scale + ')'; } - function debugTransform(d) { + function tileCenter(d) { var _ts = tileSize * Math.pow(2, z - d[2]); - var scale = tileSizeAtZoom(d, z); - return 'translate(' + - ((d[0] * _ts) - tileOrigin[0] + pixelOffset[0] + scale * (tileSize / 4)) + 'px,' + - ((d[1] * _ts) - tileOrigin[1] + pixelOffset[1] + scale * (tileSize / 2)) + 'px)'; + return [ + ((d[0] * _ts) - tileOrigin[0] + pixelOffset[0] + (_ts / 2)), + ((d[1] * _ts) - tileOrigin[1] + pixelOffset[1] + (_ts / 2)) + ]; } + function debugTransform(d) { + var coord = tileCenter(d); + return 'translate(' + coord[0] + 'px,' + coord[1] + 'px)'; + } + + + // Pick a representative tile near the center of the viewport + // (This is useful for sampling the imagery vintage) + var dims = tile.size(), + mapCenter = [dims[0] / 2, dims[1] / 2], + minDist = Math.max(dims[0], dims[1]), + nearCenter; + + requests.forEach(function(d) { + var c = tileCenter(d); + var dist = geoEuclideanDistance(c, mapCenter); + if (dist < minDist) { + minDist = dist; + nearCenter = d; + } + }); + var image = selection.selectAll('img') .data(requests, function(d) { return d[3]; }); @@ -160,6 +185,7 @@ export function rendererTileLayer(context) { image.exit() .style(transformProp, imageTransform) .classed('tile-removing', true) + .classed('tile-center', false) .each(function() { var tile = d3.select(this); window.setTimeout(function() { @@ -178,7 +204,9 @@ export function rendererTileLayer(context) { .merge(image) .style(transformProp, imageTransform) .classed('tile-debug', showDebug) - .classed('tile-removing', false); + .classed('tile-removing', false) + .classed('tile-center', function(d) { return d === nearCenter; }); + var debug = selection.selectAll('.tile-label-debug') @@ -187,12 +215,41 @@ export function rendererTileLayer(context) { debug.exit() .remove(); - debug.enter() - .append('div') - .attr('class', 'tile-label-debug') - .merge(debug) - .text(function(d) { return d[2] + ' / ' + d[0] + ' / ' + d[1]; }) - .style(transformProp, debugTransform); + if (showDebug) { + var debugEnter = debug.enter() + .append('div') + .attr('class', 'tile-label-debug'); + + debugEnter + .append('div') + .attr('class', 'tile-label-debug-coord'); + + debugEnter + .append('div') + .attr('class', 'tile-label-debug-vintage'); + + debug = debug.merge(debugEnter); + + debug + .style(transformProp, debugTransform); + + debug + .selectAll('.tile-label-debug-coord') + .text(function(d) { return d[2] + ' / ' + d[0] + ' / ' + d[1]; }); + + debug + .selectAll('.tile-label-debug-vintage') + .each(function(d) { + var span = d3.select(this); + var center = context.projection.invert(tileCenter(d)); + source.getVintage(center, d, function(err, result) { + span.text((result && result.range) || + t('info_panels.background.vintage') + ': ' + t('info_panels.background.unknown') + ); + }); + }); + } + } diff --git a/modules/services/nominatim.js b/modules/services/nominatim.js index b5434306a..4eb13eedd 100644 --- a/modules/services/nominatim.js +++ b/modules/services/nominatim.js @@ -25,32 +25,44 @@ export default { countryCode: function (location, callback) { - var countryCodes = nominatimCache.search( + this.reverse(location, function(err, result) { + if (err) { + return callback(err); + } else if (result.address) { + return callback(null, result.address.country_code); + } else { + return callback('Unable to geocode', null); + } + }); + }, + + + reverse: function (location, callback) { + var cached = nominatimCache.search( { minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] } ); - if (countryCodes.length > 0) { - return callback(null, countryCodes[0].data); + if (cached.length > 0) { + return callback(null, cached[0].data); } - var params = { format: 'json', addressdetails: 1, lat: location[1], lon: location[0] }; + var params = { zoom: 13, format: 'json', addressdetails: 1, lat: location[1], lon: location[0] }; var url = apibase + 'reverse?' + utilQsString(params); if (inflight[url]) return; inflight[url] = d3.json(url, function(err, result) { delete inflight[url]; - if (err) + if (err) { return callback(err); - else if (result && result.error) + } else if (result && result.error) { return callback(result.error); + } - var extent = geoExtent(location).padByMeters(1000); - nominatimCache.insert(_.assign(extent.bbox(), - { data: result.address.country_code } - )); + var extent = geoExtent(location).padByMeters(200); + nominatimCache.insert(_.assign(extent.bbox(), {data: result})); - callback(null, result.address.country_code); + callback(null, result); }); }, diff --git a/modules/services/osm.js b/modules/services/osm.js index de1d977e6..a395f85cd 100644 --- a/modules/services/osm.js +++ b/modules/services/osm.js @@ -104,11 +104,14 @@ var parsers = { var attrs = obj.attributes; return new osmNode({ id: osmEntity.id.fromOSM('node', attrs.id.value), - loc: getLoc(attrs), + visible: getVisible(attrs), version: attrs.version.value, + changeset: attrs.changeset && attrs.changeset.value, + timestamp: attrs.timestamp && attrs.timestamp.value, user: attrs.user && attrs.user.value, - tags: getTags(obj), - visible: getVisible(attrs) + uid: attrs.uid && attrs.uid.value, + loc: getLoc(attrs), + tags: getTags(obj) }); }, @@ -116,11 +119,14 @@ var parsers = { var attrs = obj.attributes; return new osmWay({ id: osmEntity.id.fromOSM('way', attrs.id.value), + visible: getVisible(attrs), version: attrs.version.value, + changeset: attrs.changeset && attrs.changeset.value, + timestamp: attrs.timestamp && attrs.timestamp.value, user: attrs.user && attrs.user.value, + uid: attrs.uid && attrs.uid.value, tags: getTags(obj), nodes: getNodes(obj), - visible: getVisible(attrs) }); }, @@ -128,11 +134,14 @@ var parsers = { var attrs = obj.attributes; return new osmRelation({ id: osmEntity.id.fromOSM('relation', attrs.id.value), + visible: getVisible(attrs), version: attrs.version.value, + changeset: attrs.changeset && attrs.changeset.value, + timestamp: attrs.timestamp && attrs.timestamp.value, user: attrs.user && attrs.user.value, + uid: attrs.uid && attrs.uid.value, tags: getTags(obj), - members: getMembers(obj), - visible: getVisible(attrs) + members: getMembers(obj) }); } }; @@ -194,6 +203,11 @@ export default { }, + historyURL: function(entity) { + return urlroot + '/' + entity.type + '/' + entity.osmId() + '/history'; + }, + + userURL: function(username) { return urlroot + '/user/' + username; }, @@ -347,10 +361,18 @@ export default { image_url = img[0].getAttribute('href'); } + var changesets = u.getElementsByTagName('changesets'), + changesets_count = 0; + + if (changesets && changesets[0] && changesets[0].getAttribute('count')) { + changesets_count = changesets[0].getAttribute('count'); + } + userDetails = { + id: u.attributes.id.value, display_name: u.attributes.display_name.value, image_url: image_url, - id: u.attributes.id.value + changesets_count: changesets_count }; callback(undefined, userDetails); diff --git a/modules/ui/info.js b/modules/ui/info.js index eee4d8613..6f02ca501 100644 --- a/modules/ui/info.js +++ b/modules/ui/info.js @@ -1,249 +1,124 @@ import * as d3 from 'd3'; -import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { geoExtent } from '../geo/index'; -import { utilDetect } from '../util/detect'; +import { svgIcon } from '../svg'; import { uiCmd } from './cmd'; - -import { - geoLength as d3GeoLength, - geoCentroid as d3GeoCentroid -} from 'd3'; +import { uiInfoPanels } from './panels/index'; export function uiInfo(context) { - var isImperial = (utilDetect().locale.toLowerCase() === 'en-us'), - isHidden = true; + var ids = Object.keys(uiInfoPanels), + wasActive = ['measurement'], + panels = {}, + active = {}; + + // create panels + ids.forEach(function(k) { + if (!panels[k]) { + panels[k] = uiInfoPanels[k](context); + active[k] = false; + } + }); function info(selection) { - function radiansToMeters(r) { - // using WGS84 authalic radius (6371007.1809 m) - return r * 6371007.1809; - } - - function steradiansToSqmeters(r) { - // http://gis.stackexchange.com/a/124857/40446 - return r / (4 * Math.PI) * 510065621724000; - } - - - function toLineString(feature) { - if (feature.type === 'LineString') return feature; - - var result = { type: 'LineString', coordinates: [] }; - if (feature.type === 'Polygon') { - result.coordinates = feature.coordinates[0]; - } else if (feature.type === 'MultiPolygon') { - result.coordinates = feature.coordinates[0][0]; - } - - return result; - } - - - function displayLength(m) { - var d = m * (isImperial ? 3.28084 : 1), - p, unit; - - if (isImperial) { - if (d >= 5280) { - d /= 5280; - unit = 'mi'; - } else { - unit = 'ft'; - } - } else { - if (d >= 1000) { - d /= 1000; - unit = 'km'; - } else { - unit = 'm'; - } - } - - // drop unnecessary precision - p = d > 1000 ? 0 : d > 100 ? 1 : 2; - - return String(d.toFixed(p)) + ' ' + unit; - } - - - function displayArea(m2) { - var d = m2 * (isImperial ? 10.7639111056 : 1), - d1, d2, p1, p2, unit1, unit2; - - if (isImperial) { - if (d >= 6969600) { // > 0.25mi² show mi² - d1 = d / 27878400; - unit1 = 'mi²'; - } else { - d1 = d; - unit1 = 'ft²'; - } - - if (d > 4356 && d < 43560000) { // 0.1 - 1000 acres - d2 = d / 43560; - unit2 = 'ac'; - } - - } else { - if (d >= 250000) { // > 0.25km² show km² - d1 = d / 1000000; - unit1 = 'km²'; - } else { - d1 = d; - unit1 = 'm²'; - } - - if (d > 1000 && d < 10000000) { // 0.1 - 1000 hectares - d2 = d / 10000; - unit2 = 'ha'; - } - } - - // drop unnecessary precision - p1 = d1 > 1000 ? 0 : d1 > 100 ? 1 : 2; - p2 = d2 > 1000 ? 0 : d2 > 100 ? 1 : 2; - - return String(d1.toFixed(p1)) + ' ' + unit1 + - (d2 ? ' (' + String(d2.toFixed(p2)) + ' ' + unit2 + ')' : ''); - } - - function redraw() { - if (isHidden) return; + var activeids = ids.filter(function(k) { return active[k]; }).sort(); - var resolver = context.graph(), - selected = _.filter(context.selectedIDs(), function(e) { return context.hasEntity(e); }), - singular = selected.length === 1 ? selected[0] : null, - extent = geoExtent(), - entity; + var containers = infoPanels.selectAll('.panel-container') + .data(activeids, function(k) { return k; }); - wrap.html(''); - wrap.append('h4') - .attr('class', 'infobox-heading fillD') - .text(singular || t('infobox.selected', { n: selected.length })); + containers.exit() + .style('opacity', 1) + .transition() + .duration(200) + .style('opacity', 0) + .on('end', function(d) { + d3.select(this) + .call(panels[d].off) + .remove(); + }); - if (!selected.length) return; + var enter = containers.enter() + .append('div') + .attr('class', function(d) { return 'fillD2 panel-container panel-container-' + d; }); - var center; - for (var i = 0; i < selected.length; i++) { - entity = context.entity(selected[i]); - extent._extend(entity.extent(resolver)); - } - center = extent.center(); + enter + .style('opacity', 0) + .transition() + .duration(200) + .style('opacity', 1); + + var title = enter + .append('div') + .attr('class', 'panel-title fillD2'); + + title + .append('h3') + .text(function(d) { return panels[d].title; }); + + title + .append('button') + .attr('class', 'close') + .on('click', function (d) { toggle(d); }) + .call(svgIcon('#icon-close')); + + enter + .append('div') + .attr('class', function(d) { return 'panel-content panel-content-' + d; }); - var list = wrap.append('ul'); + // redraw the panels + infoPanels.selectAll('.panel-content') + .each(function(d) { + d3.select(this).call(panels[d]); + }); + } - // multiple features, just display extent center.. - if (!singular) { - list.append('li') - .text(t('infobox.center') + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); - return; - } - // single feature, display details.. - if (!entity) return; - var geometry = entity.geometry(resolver); + function toggle(which) { + if (d3.event) d3.event.preventDefault(); - if (geometry === 'line' || geometry === 'area') { - var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate()), - feature = entity.asGeoJSON(resolver), - length = radiansToMeters(d3GeoLength(toLineString(feature))), - lengthLabel = t('infobox.' + (closed ? 'perimeter' : 'length')), - centroid = d3GeoCentroid(feature); + var activeids = ids.filter(function(k) { return active[k]; }); - list.append('li') - .text(t('infobox.geometry') + ': ' + - (closed ? t('infobox.closed') + ' ' : '') + t('geometry.' + geometry) ); - - if (closed) { - var area = steradiansToSqmeters(entity.area(resolver)); - list.append('li') - .text(t('infobox.area') + ': ' + displayArea(area)); + if (which) { // toggle one + active[which] = !active[which]; + if (activeids.length === 1 && activeids[0] === which) { // none active anymore + wasActive = [which]; + } + } else { // toggle all + if (activeids.length) { + wasActive = activeids; + activeids.forEach(function(k) { active[k] = false; }); + } else { + wasActive.forEach(function(k) { active[k] = true; }); } - - list.append('li') - .text(lengthLabel + ': ' + displayLength(length)); - - list.append('li') - .text(t('infobox.centroid') + ': ' + centroid[0].toFixed(5) + ', ' + centroid[1].toFixed(5)); - - - var toggle = isImperial ? 'imperial' : 'metric'; - wrap.append('a') - .text(t('infobox.' + toggle)) - .attr('href', '#') - .attr('class', 'button') - .on('click', function() { - d3.event.preventDefault(); - isImperial = !isImperial; - redraw(); - }); - - } else { - var centerLabel = t('infobox.' + (entity.type === 'node' ? 'location' : 'center')); - - list.append('li') - .text(t('infobox.geometry') + ': ' + t('geometry.' + geometry)); - - list.append('li') - .text(centerLabel + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); } + + redraw(); } - function toggle() { - if (d3.event) { - d3.event.preventDefault(); - } - - isHidden = !isHidden; - - if (isHidden) { - wrap - .style('display', 'block') - .style('opacity', 1) - .transition() - .duration(200) - .style('opacity', 0) - .on('end', function() { - d3.select(this).style('display', 'none'); - }); - } else { - wrap - .style('display', 'block') - .style('opacity', 0) - .transition() - .duration(200) - .style('opacity', 1) - .on('end', function() { - redraw(); - }); - } - } - - - var wrap = selection.selectAll('.infobox') + var infoPanels = selection.selectAll('.info-panels') .data([0]); - wrap = wrap.enter() + infoPanels = infoPanels.enter() .append('div') - .attr('class', 'infobox fillD2') - .style('display', (isHidden ? 'none' : 'block')) - .merge(wrap); - - context.map() - .on('drawn.info', redraw); + .attr('class', 'info-panels') + .merge(infoPanels); redraw(); var keybinding = d3keybinding('info') - .on(uiCmd('⌘' + t('infobox.key')), toggle); + .on(uiCmd('⌘' + t('info_panels.key')), toggle); + + ids.forEach(function(k) { + var key = t('info_panels.' + k + '.key', { default: null }); + if (!key) return; + keybinding + .on(uiCmd('⌘⇧' + key), function() { toggle(k); }); + }); d3.select(document) .call(keybinding); diff --git a/modules/ui/init.js b/modules/ui/init.js index cdfb5c6d4..5a88f51cd 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -81,10 +81,7 @@ export function uiInit(context) { .call(map); content - .call(uiMapInMap(context)); - - content - .append('div') + .call(uiMapInMap(context)) .call(uiInfo(context)); bar diff --git a/modules/ui/panels/background.js b/modules/ui/panels/background.js new file mode 100644 index 000000000..44725af1b --- /dev/null +++ b/modules/ui/panels/background.js @@ -0,0 +1,110 @@ +import * as d3 from 'd3'; +import _ from 'lodash'; +import { t } from '../../util/locale'; + + +export function uiPanelBackground(context) { + var background = context.background(); + var currSource = null; + var currZoom = ''; + var currVintage = ''; + + + function redraw(selection) { + if (currSource !== background.baseLayerSource().name()) { + currSource = background.baseLayerSource().name(); + currZoom = ''; + currVintage = ''; + } + + selection.html(''); + + var list = selection + .append('ul') + .attr('class', 'background-info'); + + list + .append('li') + .text(currSource); + + list + .append('li') + .text(t('info_panels.background.zoom') + ': ') + .append('span') + .attr('class', 'zoom') + .text(currZoom); + + list + .append('li') + .text(t('info_panels.background.vintage') + ': ') + .append('span') + .attr('class', 'vintage') + .text(currVintage); + + if (!currVintage) { + debouncedGetVintage(selection); + } + + var toggle = context.getDebug('tile') ? 'hide_tiles' : 'show_tiles'; + + selection + .append('a') + .text(t('info_panels.background.' + toggle)) + .attr('href', '#') + .attr('class', 'button button-toggle-tiles') + .on('click', function() { + d3.event.preventDefault(); + context.setDebug('tile', !context.getDebug('tile')); + selection.call(redraw); + }); + } + + + var debouncedGetVintage = _.debounce(getVintage, 250); + function getVintage(selection) { + var tile = d3.select('.layer-background img.tile-center'); // tile near viewport center + if (tile.empty()) return; + + var d = tile.datum(), + zoom = (d && d.length >= 3 && d[2]) || Math.floor(context.map().zoom()), + center = context.map().center(); + + currZoom = String(zoom); + selection.selectAll('.zoom') + .text(currZoom); + + if (!d || !d.length >= 3) return; + background.baseLayerSource().getVintage(center, d, function(err, result) { + currVintage = (result && result.range) || t('info_panels.background.unknown'); + selection.selectAll('.vintage') + .text(currVintage); + }); + } + + + var panel = function(selection) { + selection.call(redraw); + + context.map() + .on('drawn.info-background', function() { + selection.call(redraw); + }) + .on('move.info-background', function() { + selection.call(debouncedGetVintage); + }); + + }; + + panel.off = function() { + context.map() + .on('drawn.info-background', null) + .on('move.info-background', null); + }; + + panel.id = 'background'; + panel.title = t('info_panels.background.title'); + panel.key = t('info_panels.background.key'); + + + return panel; +} diff --git a/modules/ui/panels/history.js b/modules/ui/panels/history.js new file mode 100644 index 000000000..a1c604d00 --- /dev/null +++ b/modules/ui/panels/history.js @@ -0,0 +1,157 @@ +import _ from 'lodash'; +import { t } from '../../util/locale'; +import { svgIcon } from '../../svg'; + + +export function uiPanelHistory(context) { + + + function displayTimestamp(entity) { + if (!entity.timestamp) return t('info_panels.history.unknown'); + + var d = new Date(entity.timestamp); + if (isNaN(d.getTime())) return t('info_panels.history.unknown'); + + return d.toLocaleString(); + } + + + function displayUser(selection, entity) { + if (!entity.user) { + selection + .append('span') + .text(t('info_panels.history.unknown')); + return; + } + + selection + .append('span') + .attr('class', 'user-name') + .text(entity.user); + + var links = selection + .append('div') + .attr('class', 'links'); + + links + .append('a') + .attr('class', 'user-osm-link') + .attr('href', context.connection().userURL(entity.user)) + .attr('target', '_blank') + .attr('tabindex', -1) + .text('OSM'); + + links + .append('a') + .attr('class', 'user-hdyc-link') + .attr('href', 'https://hdyc.neis-one.org/?' + entity.user) + .attr('target', '_blank') + .attr('tabindex', -1) + .text('HDYC'); + } + + + function displayChangeset(selection, entity) { + if (!entity.changeset) { + selection + .append('span') + .text(t('info_panels.history.unknown')); + return; + } + + selection + .append('span') + .attr('class', 'changeset-id') + .text(entity.changeset); + + var links = selection + .append('div') + .attr('class', 'links'); + + links + .append('a') + .attr('class', 'changeset-osm-link') + .attr('href', context.connection().changesetURL(entity.changeset)) + .attr('target', '_blank') + .attr('tabindex', -1) + .text('OSM'); + + links + .append('a') + .attr('class', 'changeset-osmcha-link') + .attr('href', 'https://osmcha.mapbox.com/changesets/' + entity.changeset) + .attr('target', '_blank') + .attr('tabindex', -1) + .text('OSMCha'); + } + + + function redraw(selection) { + var selected = _.filter(context.selectedIDs(), function(e) { return context.hasEntity(e); }), + singular = selected.length === 1 ? selected[0] : null; + + selection.html(''); + + selection + .append('h4') + .attr('class', 'history-heading') + .text(singular || t('info_panels.history.selected', { n: selected.length })); + + if (!singular) return; + + var entity = context.entity(singular); + + var list = selection + .append('ul'); + + list + .append('li') + .text(t('info_panels.history.version') + ': ' + entity.version); + + list + .append('li') + .text(t('info_panels.history.last_edit') + ': ' + displayTimestamp(entity)); + + list + .append('li') + .text(t('info_panels.history.edited_by') + ': ') + .call(displayUser, entity); + + list + .append('li') + .text(t('info_panels.history.changeset') + ': ') + .call(displayChangeset, entity); + + selection + .append('a') + .attr('class', 'view-history-on-osm') + .attr('target', '_blank') + .attr('tabindex', -1) + .attr('href', context.connection().historyURL(entity)) + .call(svgIcon('#icon-out-link', 'inline')) + .append('span') + .text(t('info_panels.history.link_text')); + } + + + var panel = function(selection) { + selection.call(redraw); + + context.map() + .on('drawn.info-history', function() { + selection.call(redraw); + }); + }; + + panel.off = function() { + context.map() + .on('drawn.info-history', null); + }; + + panel.id = 'history'; + panel.title = t('info_panels.history.title'); + panel.key = t('info_panels.history.key'); + + + return panel; +} diff --git a/modules/ui/panels/index.js b/modules/ui/panels/index.js new file mode 100644 index 000000000..b7a283445 --- /dev/null +++ b/modules/ui/panels/index.js @@ -0,0 +1,16 @@ +export * from './background'; +export * from './history'; +export * from './location'; +export * from './measurement'; + +import { uiPanelBackground } from './background'; +import { uiPanelHistory } from './history'; +import { uiPanelLocation } from './location'; +import { uiPanelMeasurement } from './measurement'; + +export var uiInfoPanels = { + background: uiPanelBackground, + history: uiPanelHistory, + location: uiPanelLocation, + measurement: uiPanelMeasurement, +}; diff --git a/modules/ui/panels/location.js b/modules/ui/panels/location.js new file mode 100644 index 000000000..a0051ab50 --- /dev/null +++ b/modules/ui/panels/location.js @@ -0,0 +1,88 @@ +import _ from 'lodash'; +import { t } from '../../util/locale'; +import { services } from '../../services'; + + +export function uiPanelLocation(context) { + var currLocation = ''; + var OSM_PRECISION = 7; + + + function wrap(x, min, max) { + var d = max - min; + return ((x - min) % d + d) % d + min; + } + + + function clamp(x, min, max) { + return Math.max(min, Math.min(x, max)); + } + + + function redraw(selection) { + selection.html(''); + + var list = selection + .append('ul'); + + // Mouse coordinates + var coord = context.map().mouseCoordinates(); + if (coord.some(isNaN)) { + coord = context.map().center(); + } + + var coordStr = + clamp(coord[1], -90, 90).toFixed(OSM_PRECISION) + ', ' + + wrap(coord[0], -180, 180).toFixed(OSM_PRECISION); + + list + .append('li') + .text(coordStr); + + // Location Info + selection + .append('div') + .attr('class', 'location-info') + .text(currLocation || ' '); + + debouncedGetLocation(selection, coord); + } + + + var debouncedGetLocation = _.debounce(getLocation, 250); + function getLocation(selection, coord) { + if (!services.geocoder) { + currLocation = t('info_panels.location.unknown_location'); + selection.selectAll('.location-info') + .text(currLocation); + } else { + services.geocoder.reverse(coord, function(err, result) { + currLocation = result ? result.display_name : t('info_panels.location.unknown_location'); + selection.selectAll('.location-info') + .text(currLocation); + }); + } + } + + + var panel = function(selection) { + selection.call(redraw); + + context.surface() + .on('mousemove.info-location', function() { + selection.call(redraw); + }); + }; + + panel.off = function() { + context.surface() + .on('mousemove.info-location', null); + }; + + panel.id = 'location'; + panel.title = t('info_panels.location.title'); + panel.key = t('info_panels.location.key'); + + + return panel; +} diff --git a/modules/ui/panels/measurement.js b/modules/ui/panels/measurement.js new file mode 100644 index 000000000..3c9345a97 --- /dev/null +++ b/modules/ui/panels/measurement.js @@ -0,0 +1,232 @@ +import * as d3 from 'd3'; +import _ from 'lodash'; +import { t } from '../../util/locale'; +import { geoExtent } from '../../geo'; +import { utilDetect } from '../../util/detect'; + +import { + geoLength as d3GeoLength, + geoCentroid as d3GeoCentroid +} from 'd3'; + + +export function uiPanelMeasurement(context) { + var isImperial = (utilDetect().locale.toLowerCase() === 'en-us'); + var OSM_PRECISION = 7; + + + function radiansToMeters(r) { + // using WGS84 authalic radius (6371007.1809 m) + return r * 6371007.1809; + } + + function steradiansToSqmeters(r) { + // http://gis.stackexchange.com/a/124857/40446 + return r / (4 * Math.PI) * 510065621724000; + } + + + function toLineString(feature) { + if (feature.type === 'LineString') return feature; + + var result = { type: 'LineString', coordinates: [] }; + if (feature.type === 'Polygon') { + result.coordinates = feature.coordinates[0]; + } else if (feature.type === 'MultiPolygon') { + result.coordinates = feature.coordinates[0][0]; + } + + return result; + } + + + function displayLength(m) { + var d = m * (isImperial ? 3.28084 : 1), + p, unit; + + if (isImperial) { + if (d >= 5280) { + d /= 5280; + unit = 'mi'; + } else { + unit = 'ft'; + } + } else { + if (d >= 1000) { + d /= 1000; + unit = 'km'; + } else { + unit = 'm'; + } + } + + // drop unnecessary precision + p = d > 1000 ? 0 : d > 100 ? 1 : 2; + + return String(d.toFixed(p)) + ' ' + unit; + } + + + function displayArea(m2) { + var d = m2 * (isImperial ? 10.7639111056 : 1), + d1, d2, p1, p2, unit1, unit2; + + if (isImperial) { + if (d >= 6969600) { // > 0.25mi² show mi² + d1 = d / 27878400; + unit1 = 'mi²'; + } else { + d1 = d; + unit1 = 'ft²'; + } + + if (d > 4356 && d < 43560000) { // 0.1 - 1000 acres + d2 = d / 43560; + unit2 = 'ac'; + } + + } else { + if (d >= 250000) { // > 0.25km² show km² + d1 = d / 1000000; + unit1 = 'km²'; + } else { + d1 = d; + unit1 = 'm²'; + } + + if (d > 1000 && d < 10000000) { // 0.1 - 1000 hectares + d2 = d / 10000; + unit2 = 'ha'; + } + } + + // drop unnecessary precision + p1 = d1 > 1000 ? 0 : d1 > 100 ? 1 : 2; + p2 = d2 > 1000 ? 0 : d2 > 100 ? 1 : 2; + + return String(d1.toFixed(p1)) + ' ' + unit1 + + (d2 ? ' (' + String(d2.toFixed(p2)) + ' ' + unit2 + ')' : ''); + } + + + function redraw(selection) { + var resolver = context.graph(), + selected = _.filter(context.selectedIDs(), function(e) { return context.hasEntity(e); }), + singular = selected.length === 1 ? selected[0] : null, + extent = geoExtent(), + entity; + + selection.html(''); + + selection + .append('h4') + .attr('class', 'measurement-heading') + .text(singular || t('info_panels.measurement.selected', { n: selected.length })); + + if (!selected.length) return; + + var center; + for (var i = 0; i < selected.length; i++) { + entity = context.entity(selected[i]); + extent._extend(entity.extent(resolver)); + } + center = extent.center(); + + + var list = selection + .append('ul'); + + // multiple features, just display extent center.. + if (!singular) { + list + .append('li') + .text(t('info_panels.measurement.center') + ': ' + + center[0].toFixed(OSM_PRECISION) + ', ' + center[1].toFixed(OSM_PRECISION) + ); + return; + } + + // single feature, display details.. + if (!entity) return; + var geometry = entity.geometry(resolver); + + if (geometry === 'line' || geometry === 'area') { + var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate()), + feature = entity.asGeoJSON(resolver), + length = radiansToMeters(d3GeoLength(toLineString(feature))), + lengthLabel = t('info_panels.measurement.' + (closed ? 'perimeter' : 'length')), + centroid = d3GeoCentroid(feature); + + list + .append('li') + .text(t('info_panels.measurement.geometry') + ': ' + + (closed ? t('info_panels.measurement.closed') + ' ' : '') + t('geometry.' + geometry) ); + + if (closed) { + var area = steradiansToSqmeters(entity.area(resolver)); + list + .append('li') + .text(t('info_panels.measurement.area') + ': ' + displayArea(area)); + } + + list + .append('li') + .text(lengthLabel + ': ' + displayLength(length)); + + list + .append('li') + .text(t('info_panels.measurement.centroid') + ': ' + + centroid[0].toFixed(OSM_PRECISION) + ', ' + centroid[1].toFixed(OSM_PRECISION) + ); + + + var toggle = isImperial ? 'imperial' : 'metric'; + + selection + .append('a') + .text(t('info_panels.measurement.' + toggle)) + .attr('href', '#') + .attr('class', 'button button-toggle-units') + .on('click', function() { + d3.event.preventDefault(); + isImperial = !isImperial; + selection.call(redraw); + }); + + } else { + var centerLabel = t('info_panels.measurement.' + (entity.type === 'node' ? 'location' : 'center')); + + list + .append('li') + .text(t('info_panels.measurement.geometry') + ': ' + t('geometry.' + geometry)); + + list + .append('li') + .text(centerLabel + ': ' + + center[0].toFixed(OSM_PRECISION) + ', ' + center[1].toFixed(OSM_PRECISION) + ); + } + } + + + var panel = function(selection) { + selection.call(redraw); + + context.map() + .on('drawn.info-measurement', function() { + selection.call(redraw); + }); + }; + + panel.off = function() { + context.map() + .on('drawn.info-measurement', null); + }; + + panel.id = 'measurement'; + panel.title = t('info_panels.measurement.title'); + panel.key = t('info_panels.measurement.key'); + + + return panel; +} diff --git a/modules/ui/shortcuts.js b/modules/ui/shortcuts.js index bf5245990..2e2a63d8f 100644 --- a/modules/ui/shortcuts.js +++ b/modules/ui/shortcuts.js @@ -105,7 +105,7 @@ export function uiShortcuts() { var shortcutsEnter = shortcuts .enter() .append('div') - .attr('class', 'shortcut-tab'); + .attr('class', function(d) { return 'shortcut-tab shortcut-tab-' + d.tab; }); var columnsEnter = shortcutsEnter .selectAll('.shortcut-column') diff --git a/scripts/deploy.sh b/scripts/deploy.sh old mode 100644 new mode 100755 diff --git a/scripts/txpush.sh b/scripts/txpush.sh new file mode 100755 index 000000000..84b36fcac --- /dev/null +++ b/scripts/txpush.sh @@ -0,0 +1,21 @@ +#/bin/bash + +# This script runs on TravisCI to push new translation strings to transifex + +echo "TRAVIS=$TRAVIS" +echo "TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST" +echo "TRAVIS_BRANCH=$TRAVIS_BRANCH" +echo "TRAVIS_NODE_VERSION=$TRAVIS_NODE_VERSION" + +if [[ "$TRAVIS" != "true" ]]; then exit 0; fi +if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then exit 0; fi +if [[ "$TRAVIS_BRANCH" != "master" ]]; then exit 0; fi +if [[ "$TRAVIS_NODE_VERSION" != "6" ]]; then exit 0; fi + +echo "Pushing source strings to Transifex..." +pip install virtualenv +virtualenv ~/env +source ~/env/bin/activate +pip install transifex-client +sudo echo $'[https://www.transifex.com]\nhostname = https://www.transifex.com\nusername = '"$TRANSIFEX_USER"$'\npassword = '"$TRANSIFEX_PASSWORD"$'\n' > ~/.transifexrc +tx push -s diff --git a/test/spec/services/nominatim.js b/test/spec/services/nominatim.js index 554479834..37e5a60ee 100644 --- a/test/spec/services/nominatim.js +++ b/test/spec/services/nominatim.js @@ -17,7 +17,6 @@ describe('iD.serviceNominatim', function() { describe('#countryCode', function() { - it('calls the given callback with the results of the country code query', function() { var callback = sinon.spy(); nominatim.countryCode([16, 48], callback); @@ -28,13 +27,15 @@ describe('iD.serviceNominatim', function() { server.respond(); expect(query(server.requests[0].url)).to.eql( - {format: 'json', addressdetails: '1', lat: '48', lon: '16'}); + {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'}); expect(callback).to.have.been.calledWithExactly(null, 'at'); }); + }); - it('should not cache the first country code result', function() { + describe('#reverse', function() { + it('should not cache distant result', function() { var callback = sinon.spy(); - nominatim.countryCode([16, 48], callback); + nominatim.reverse([16, 48], callback); server.respondWith('GET', new RegExp('https://nominatim.openstreetmap.org/reverse'), [200, { 'Content-Type': 'application/json' }, @@ -42,13 +43,14 @@ describe('iD.serviceNominatim', function() { server.respond(); expect(query(server.requests[0].url)).to.eql( - {format: 'json', addressdetails: '1', lat: '48', lon: '16'}); - expect(callback).to.have.been.calledWithExactly(null, 'at'); + {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'}); + expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); server.restore(); server = sinon.fakeServer.create(); - nominatim.countryCode([17, 49], callback); + callback = sinon.spy(); + nominatim.reverse([17, 49], callback); server.respondWith('GET', new RegExp('https://nominatim.openstreetmap.org/reverse'), [200, { 'Content-Type': 'application/json' }, @@ -56,13 +58,13 @@ describe('iD.serviceNominatim', function() { server.respond(); expect(query(server.requests[0].url)).to.eql( - {format: 'json', addressdetails: '1', lat: '49', lon: '17'}); - expect(callback).to.have.been.calledWithExactly(null, 'cz'); + {zoom: '13', format: 'json', addressdetails: '1', lat: '49', lon: '17'}); + expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'cz'}}); }); - it('should cache the first country code result', function() { + it('should cache nearby result', function() { var callback = sinon.spy(); - nominatim.countryCode([16, 48], callback); + nominatim.reverse([16, 48], callback); server.respondWith('GET', new RegExp('https://nominatim.openstreetmap.org/reverse'), [200, { 'Content-Type': 'application/json' }, @@ -70,24 +72,25 @@ describe('iD.serviceNominatim', function() { server.respond(); expect(query(server.requests[0].url)).to.eql( - {format: 'json', addressdetails: '1', lat: '48', lon: '16'}); - expect(callback).to.have.been.calledWithExactly(null, 'at'); + {zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16'}); + expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); server.restore(); server = sinon.fakeServer.create(); - nominatim.countryCode([16.01, 48.01], callback); + callback = sinon.spy(); + nominatim.reverse([16.000001, 48.000001], callback); server.respondWith('GET', new RegExp('https://nominatim.openstreetmap.org/reverse'), [200, { 'Content-Type': 'application/json' }, '{"address":{"country_code":"cz"}}']); server.respond(); - expect(callback).to.have.been.calledWithExactly(null, 'at'); + expect(callback).to.have.been.calledWithExactly(null, {address: {country_code:'at'}}); }); it('calls the given callback with an error', function() { var callback = sinon.spy(); - nominatim.countryCode([1000, 1000], callback); + nominatim.reverse([1000, 1000], callback); server.respondWith('GET', new RegExp('https://nominatim.openstreetmap.org/reverse'), [200, { 'Content-Type': 'application/json' }, @@ -95,7 +98,7 @@ describe('iD.serviceNominatim', function() { server.respond(); expect(query(server.requests[0].url)).to.eql( - {format: 'json', addressdetails: '1', lat: '1000', lon: '1000'}); + {zoom: '13', format: 'json', addressdetails: '1', lat: '1000', lon: '1000'}); expect(callback).to.have.been.calledWithExactly('Unable to geocode'); }); }); diff --git a/test/spec/services/osm.js b/test/spec/services/osm.js index 4644b310b..259a0cc7d 100644 --- a/test/spec/services/osm.js +++ b/test/spec/services/osm.js @@ -39,12 +39,50 @@ describe('iD.serviceOsm', function () { expect(connection.changesetURL(2)).to.match(/^https:/); }); - describe('#changesetUrl', function() { + describe('#changesetURL', function() { it('provides a changeset url', function() { expect(connection.changesetURL(2)).to.eql('http://www.openstreetmap.org/changeset/2'); }); }); + describe('#changesetsURL', function() { + it('provides a local changesets url', function() { + var center = [-74.65, 40.65]; + var zoom = 17; + expect(connection.changesetsURL(center, zoom)).to.eql('http://www.openstreetmap.org/history#map=17/40.65000/-74.65000'); + }); + }); + + describe('#entityURL', function() { + it('provides an entity url for a node', function() { + var e = iD.Node({id: 'n1'}); + expect(connection.entityURL(e)).to.eql('http://www.openstreetmap.org/node/1'); + }); + it('provides an entity url for a way', function() { + var e = iD.Way({id: 'w1'}); + expect(connection.entityURL(e)).to.eql('http://www.openstreetmap.org/way/1'); + }); + it('provides an entity url for a relation', function() { + var e = iD.Relation({id: 'r1'}); + expect(connection.entityURL(e)).to.eql('http://www.openstreetmap.org/relation/1'); + }); + }); + + describe('#historyURL', function() { + it('provides a history url for a node', function() { + var e = iD.Node({id: 'n1'}); + expect(connection.historyURL(e)).to.eql('http://www.openstreetmap.org/node/1/history'); + }); + it('provides a history url for a way', function() { + var e = iD.Way({id: 'w1'}); + expect(connection.historyURL(e)).to.eql('http://www.openstreetmap.org/way/1/history'); + }); + it('provides a history url for a relation', function() { + var e = iD.Relation({id: 'r1'}); + expect(connection.historyURL(e)).to.eql('http://www.openstreetmap.org/relation/1/history'); + }); + }); + describe('#userURL', function() { it('provides a user url', function() { expect(connection.userURL('bob')).to.eql('http://www.openstreetmap.org/user/bob');