modules/ui/sections/data_layers.js (360 lines of code) (raw):

import _debounce from 'lodash-es/debounce'; import { select as d3_select } from 'd3-selection'; import { prefs } from '../../core/preferences'; import { t, localizer } from '../../core/localizer'; import { uiTooltip } from '../tooltip'; import { svgIcon } from '../../svg/icon'; import { Extent } from '@id-sdk/extent'; import { modeBrowse } from '../../modes/browse'; import { uiCmd } from '../cmd'; import { uiSection } from '../section'; import { uiSettingsCustomData } from '../settings/custom_data'; export function uiSectionDataLayers(context) { var settingsCustomData = uiSettingsCustomData(context) .on('change', customChanged); var layers = context.layers(); var section = uiSection('data-layers', context) .label(t.html('map_data.data_layers')) .disclosureContent(renderDisclosureContent); function renderDisclosureContent(selection) { var container = selection.selectAll('.data-layer-container') .data([0]); container.enter() .append('div') .attr('class', 'data-layer-container') .merge(container) .call(drawOsmItems) .call(drawQAItems) .call(drawCustomDataItems) .call(drawVectorItems) // Beta - Detroit mapping challenge .call(drawPanelItems); } function showsLayer(which) { var layer = layers.layer(which); if (layer) { return layer.enabled(); } return false; } function setLayer(which, enabled) { // Don't allow layer changes while drawing - #6584 var mode = context.mode(); if (mode && /^draw/.test(mode.id)) return; var layer = layers.layer(which); if (layer) { layer.enabled(enabled); if (!enabled && (which === 'osm' || which === 'notes')) { context.enter(modeBrowse(context)); } } } function toggleLayer(which) { setLayer(which, !showsLayer(which)); } function drawOsmItems(selection) { var osmKeys = ['osm', 'notes']; var osmLayers = layers.all().filter(function(obj) { return osmKeys.indexOf(obj.id) !== -1; }); var ul = selection .selectAll('.layer-list-osm') .data([0]); ul = ul.enter() .append('ul') .attr('class', 'layer-list layer-list-osm') .merge(ul); var li = ul.selectAll('.list-item') .data(osmLayers); li.exit() .remove(); var liEnter = li.enter() .append('li') .attr('class', function(d) { return 'list-item list-item-' + d.id; }); var labelEnter = liEnter .append('label') .each(function(d) { if (d.id === 'osm') { d3_select(this) .call(uiTooltip() .title(t.html('map_data.layers.' + d.id + '.tooltip')) .keys([uiCmd('⌥' + t('area_fill.wireframe.key'))]) .placement('bottom') ); } else { d3_select(this) .call(uiTooltip() .title(t.html('map_data.layers.' + d.id + '.tooltip')) .placement('bottom') ); } }); labelEnter .append('input') .attr('type', 'checkbox') .on('change', function(d3_event, d) { toggleLayer(d.id); }); labelEnter .append('span') .html(function(d) { return t.html('map_data.layers.' + d.id + '.title'); }); // Update li .merge(liEnter) .classed('active', function (d) { return d.layer.enabled(); }) .selectAll('input') .property('checked', function (d) { return d.layer.enabled(); }); } function drawQAItems(selection) { var qaKeys = ['keepRight', 'improveOSM', 'osmose']; var qaLayers = layers.all().filter(function(obj) { return qaKeys.indexOf(obj.id) !== -1; }); var ul = selection .selectAll('.layer-list-qa') .data([0]); ul = ul.enter() .append('ul') .attr('class', 'layer-list layer-list-qa') .merge(ul); var li = ul.selectAll('.list-item') .data(qaLayers); li.exit() .remove(); var liEnter = li.enter() .append('li') .attr('class', function(d) { return 'list-item list-item-' + d.id; }); var labelEnter = liEnter .append('label') .each(function(d) { d3_select(this) .call(uiTooltip() .title(t.html('map_data.layers.' + d.id + '.tooltip')) .placement('bottom') ); }); labelEnter .append('input') .attr('type', 'checkbox') .on('change', function(d3_event, d) { toggleLayer(d.id); }); labelEnter .append('span') .html(function(d) { return t.html('map_data.layers.' + d.id + '.title'); }); // Update li .merge(liEnter) .classed('active', function (d) { return d.layer.enabled(); }) .selectAll('input') .property('checked', function (d) { return d.layer.enabled(); }); } // Beta feature - sample vector layers to support Detroit Mapping Challenge // https://github.com/osmus/detroit-mapping-challenge function drawVectorItems(selection) { var dataLayer = layers.layer('data'); var vtData = [ { name: 'Detroit Neighborhoods/Parks', src: 'neighborhoods-parks', tooltip: 'Neighborhood boundaries and parks as compiled by City of Detroit in concert with community groups.', template: 'https://{switch:a,b,c,d}.tiles.mapbox.com/v4/jonahadkins.cjksmur6x34562qp9iv1u3ksf-54hev,jonahadkins.cjksmqxdx33jj2wp90xd9x2md-4e5y2/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1Ijoiam9uYWhhZGtpbnMiLCJhIjoiRlVVVkx3VSJ9.9sdVEK_B_VkEXPjssU5MqA' }, { name: 'Detroit Composite POIs', src: 'composite-poi', tooltip: 'Fire Inspections, Business Licenses, and other public location data collated from the City of Detroit.', template: 'https://{switch:a,b,c,d}.tiles.mapbox.com/v4/jonahadkins.cjksmm6a02sli31myxhsr7zf3-2sw8h/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1Ijoiam9uYWhhZGtpbnMiLCJhIjoiRlVVVkx3VSJ9.9sdVEK_B_VkEXPjssU5MqA' }, { name: 'Detroit All-The-Places POIs', src: 'alltheplaces-poi', tooltip: 'Public domain business location data created by web scrapers.', template: 'https://{switch:a,b,c,d}.tiles.mapbox.com/v4/jonahadkins.cjksmswgk340g2vo06p1w9w0j-8fjjc/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1Ijoiam9uYWhhZGtpbnMiLCJhIjoiRlVVVkx3VSJ9.9sdVEK_B_VkEXPjssU5MqA' } ]; // Only show this if the map is around Detroit.. var detroit = new Extent([-83.5, 42.1], [-82.8, 42.5]); var mapCenter = new Extent(context.map().center()); var showVectorItems = (context.map().zoom() > 9 && detroit.contains(mapCenter)); var container = selection.selectAll('.vectortile-container') .data(showVectorItems ? [0] : []); container.exit() .remove(); var containerEnter = container.enter() .append('div') .attr('class', 'vectortile-container'); containerEnter .append('h4') .attr('class', 'vectortile-header') .html('Detroit Vector Tiles (Beta)'); containerEnter .append('ul') .attr('class', 'layer-list layer-list-vectortile'); containerEnter .append('div') .attr('class', 'vectortile-footer') .append('a') .attr('target', '_blank') .call(svgIcon('#iD-icon-out-link', 'inline')) .attr('href', 'https://github.com/osmus/detroit-mapping-challenge') .append('span') .html('About these layers'); container = container .merge(containerEnter); var ul = container.selectAll('.layer-list-vectortile'); var li = ul.selectAll('.list-item') .data(vtData); li.exit() .remove(); var liEnter = li.enter() .append('li') .attr('class', function(d) { return 'list-item list-item-' + d.src; }); var labelEnter = liEnter .append('label') .each(function(d) { d3_select(this).call( uiTooltip().title(d.tooltip).placement('top') ); }); labelEnter .append('input') .attr('type', 'radio') .attr('name', 'vectortile') .on('change', selectVTLayer); labelEnter .append('span') .html(function(d) { return d.name; }); // Update li .merge(liEnter) .classed('active', isVTLayerSelected) .selectAll('input') .property('checked', isVTLayerSelected); function isVTLayerSelected(d) { return dataLayer && dataLayer.template() === d.template; } function selectVTLayer(d3_event, d) { prefs('settings-custom-data-url', d.template); if (dataLayer) { dataLayer.template(d.template, d.src); dataLayer.enabled(true); } } } function drawCustomDataItems(selection) { var dataLayer = layers.layer('data'); var hasData = dataLayer && dataLayer.hasData(); var showsData = hasData && dataLayer.enabled(); var ul = selection .selectAll('.layer-list-data') .data(dataLayer ? [0] : []); // Exit ul.exit() .remove(); // Enter var ulEnter = ul.enter() .append('ul') .attr('class', 'layer-list layer-list-data'); var liEnter = ulEnter .append('li') .attr('class', 'list-item-data'); var labelEnter = liEnter .append('label') .call(uiTooltip() .title(t.html('map_data.layers.custom.tooltip')) .placement('top') ); labelEnter .append('input') .attr('type', 'checkbox') .on('change', function() { toggleLayer('data'); }); labelEnter .append('span') .html(t.html('map_data.layers.custom.title')); liEnter .append('button') .attr('class', 'open-data-options') .call(uiTooltip() .title(t.html('settings.custom_data.tooltip')) .placement((localizer.textDirection() === 'rtl') ? 'right' : 'left') ) .on('click', function(d3_event) { d3_event.preventDefault(); editCustom(); }) .call(svgIcon('#iD-icon-more')); liEnter .append('button') .attr('class', 'zoom-to-data') .call(uiTooltip() .title(t.html('map_data.layers.custom.zoom')) .placement((localizer.textDirection() === 'rtl') ? 'right' : 'left') ) .on('click', function(d3_event) { if (d3_select(this).classed('disabled')) return; d3_event.preventDefault(); d3_event.stopPropagation(); dataLayer.fitZoom(); }) .call(svgIcon('#iD-icon-framed-dot', 'monochrome')); // Update ul = ul .merge(ulEnter); ul.selectAll('.list-item-data') .classed('active', showsData) .selectAll('label') .classed('deemphasize', !hasData) .selectAll('input') .property('disabled', !hasData) .property('checked', showsData); ul.selectAll('button.zoom-to-data') .classed('disabled', !hasData); } function editCustom() { 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 drawPanelItems(selection) { var panelsListEnter = selection.selectAll('.md-extras-list') .data([0]) .enter() .append('ul') .attr('class', 'layer-list md-extras-list'); var historyPanelLabelEnter = panelsListEnter .append('li') .attr('class', 'history-panel-toggle-item') .append('label') .call(uiTooltip() .title(t.html('map_data.history_panel.tooltip')) .keys([uiCmd('⌘⇧' + t('info_panels.history.key'))]) .placement('top') ); historyPanelLabelEnter .append('input') .attr('type', 'checkbox') .on('change', function(d3_event) { d3_event.preventDefault(); context.ui().info.toggle('history'); }); historyPanelLabelEnter .append('span') .html(t.html('map_data.history_panel.title')); var measurementPanelLabelEnter = panelsListEnter .append('li') .attr('class', 'measurement-panel-toggle-item') .append('label') .call(uiTooltip() .title(t.html('map_data.measurement_panel.tooltip')) .keys([uiCmd('⌘⇧' + t('info_panels.measurement.key'))]) .placement('top') ); measurementPanelLabelEnter .append('input') .attr('type', 'checkbox') .on('change', function(d3_event) { d3_event.preventDefault(); context.ui().info.toggle('measurement'); }); measurementPanelLabelEnter .append('span') .html(t.html('map_data.measurement_panel.title')); } context.layers().on('change.uiSectionDataLayers', section.reRender); context.map() .on('move.uiSectionDataLayers', _debounce(function() { // Detroit layers may have moved in or out of view window.requestIdleCallback(section.reRender); }, 1000) ); return section; }