modules/core/rapid_context.js (161 lines of code) (raw):
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { gpx } from '@tmcw/togeojson';
import { Extent } from '@id-sdk/math';
import { utilQsString, utilStringQs } from '@id-sdk/util';
import { localizer, t } from '../core/localizer';
import { services } from '../services';
import { utilRebind } from '../util';
export function coreRapidContext(context) {
const dispatch = d3_dispatch('task_extent_set');
let _rapidContext = {};
_rapidContext.version = '1.1.8';
_rapidContext.showPowerUser = context.initialHashParams.poweruser === 'true';
function distinct(value, index, self) {
return self.indexOf(value) === index;
}
/* Task extents */
let _taskExtent;
let _isTaskBoundsRect;
_rapidContext.setTaskExtentByGpxData = function(gpxData) {
const dom = (new DOMParser()).parseFromString(gpxData, 'text/xml');
const gj = gpx(dom);
const lineStringCount = gj.features.reduce((accumulator, currentValue) => {
return accumulator + (currentValue.geometry.type === 'LineString' ? 1 : 0);
}, 0);
if (gj.type === 'FeatureCollection') {
let minlat, minlon, maxlat, maxlon;
gj.features.forEach(f => {
if (f.geometry.type === 'Point') {
const lon = f.geometry.coordinates[0];
const lat = f.geometry.coordinates[1];
if (minlat === undefined || lat < minlat) minlat = lat;
if (minlon === undefined || lon < minlon) minlon = lon;
if (maxlat === undefined || lat > maxlat) maxlat = lat;
if (maxlon === undefined || lon > maxlon) maxlon = lon;
} else if (f.geometry.type === 'LineString' && lineStringCount === 1) {
const lats = f.geometry.coordinates.map(f => f[0]);
const lngs = f.geometry.coordinates.map(f => f[1]);
const uniqueLats = lats.filter(distinct);
const uniqueLngs = lngs.filter(distinct);
let eachLatHas2Lngs = true;
uniqueLats.forEach(lat => {
const lngsForThisLat = f.geometry.coordinates
.filter(coord => coord[0] === lat) // Filter the coords to the ones with this lat
.map(coord => coord[1]) // Make an array of lngs that associate with that lat
.filter(distinct); // Finally, filter for uniqueness
if (lngsForThisLat.length !== 2) {
eachLatHas2Lngs = false;
}
});
// Check for exactly two unique latitudes, two unique longitudes,
// and that each latitude was associated with exactly 2 longitudes,
if (uniqueLats.length === 2 && uniqueLngs.length === 2 && eachLatHas2Lngs) {
_isTaskBoundsRect = true;
} else {
_isTaskBoundsRect = false;
}
}
});
_taskExtent = new Extent([minlon, minlat], [maxlon, maxlat]);
dispatch.call('task_extent_set');
}
};
_rapidContext.getTaskExtent = () => _taskExtent;
_rapidContext.isTaskRectangular = () => (!!_taskExtent && _isTaskBoundsRect);
/* Sources */
_rapidContext.sources = new Set();
/* Colors */
const RAPID_MAGENTA = '#da26d3';
const COLORS = [
'#ff0000', // red
'#ffa500', // orange
'#ffd700', // gold
'#00ff00', // lime
'#00ffff', // cyan
'#1e90ff', // dodgerblue
'#da26d3', // rapid magenta
'#ffc0cb', // pink
'#d3d3d3', // lightgray
'#faf0e6' // linen
];
_rapidContext.colors = () => COLORS;
/* Available datasets */
let _datasets = {};
_rapidContext.datasets = () => _datasets;
_rapidContext.init = () => {
localizer.ensureLoaded()
.then(() => {
_datasets = {
// setup the built-in datasets
'fbRoads': {
id: 'fbRoads',
beta: false,
added: true, // whether it should appear in the list
enabled: false, // whether the user has checked it on
conflated: true,
service: 'fbml',
color: RAPID_MAGENTA,
label: t('rapid_feature_toggle.fbRoads.label'),
license_markdown: t('rapid_feature_toggle.fbRoads.license_markdown')
},
'msBuildings': {
id: 'msBuildings',
beta: false,
added: true, // whether it should appear in the list
enabled: false, // whether the user has checked it on
conflated: true,
service: 'fbml',
color: RAPID_MAGENTA,
label: t('rapid_feature_toggle.msBuildings.label'),
license_markdown: t('rapid_feature_toggle.msBuildings.license_markdown')
}
};
// Parse enabled datasets from url hash
let enabled = context.initialHashParams.datasets || '';
if (!context.initialHashParams.hasOwnProperty('datasets')) {
let hash = utilStringQs(window.location.hash);
enabled = hash.datasets = 'fbRoads,msBuildings'; // assign default
if (!window.mocha) {
window.location.replace('#' + utilQsString(hash, true)); // update hash
}
}
let toLoad = new Set();
enabled.split(',').forEach(id => {
id = id.trim();
if (_datasets[id]) {
_datasets[id].enabled = true;
} else { // not a known dataset, we will need to look for it in esri
toLoad.add(id);
}
});
// Load any datasets from esri that aren't known to us
const service = services.esriData;
if (!service || !toLoad.size) return;
service.loadDatasets()
.then(results => {
toLoad.forEach(id => {
const d = results[id];
if (!d) return; // dataset with requested id not found, fail silently
// *** Code here is copied from `rapid_view_manage_datasets.js` `toggleDataset()` ***
service.loadLayer(d.id); // start fetching layer info (the mapping between attributes and tags)
const isBeta = d.groupCategories.some(cat => cat.toLowerCase() === '/categories/preview');
const isBuildings = d.groupCategories.some(cat => cat.toLowerCase() === '/categories/buildings');
const nextColor = Object.keys(_datasets).length % COLORS.length;
let dataset = {
id: d.id,
beta: isBeta,
added: true, // whether it should appear in the list
enabled: true, // whether the user has checked it on
conflated: false,
service: 'esri',
color: COLORS[nextColor],
label: d.title,
license_markdown: t('rapid_feature_toggle.esri.license_markdown')
};
if (d.extent) {
dataset.extent = new Extent(d.extent[0], d.extent[1]);
}
// Test running building layers through FBML conflation service
if (isBuildings) {
dataset.conflated = true;
dataset.service = 'fbml';
}
_datasets[d.id] = dataset; // add it
});
});
});
};
/* reset any state here */
_rapidContext.reset = () => {
_rapidContext.sources = new Set();
};
return utilRebind(_rapidContext, dispatch, 'on');
}