mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-22 19:13:35 +00:00
245 lines
7.9 KiB
JavaScript
245 lines
7.9 KiB
JavaScript
import _extend from 'lodash-es/extend';
|
|
import _find from 'lodash-es/find';
|
|
import _forEach from 'lodash-es/forEach';
|
|
|
|
import rbush from 'rbush';
|
|
|
|
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
|
import { json as d3_json } from 'd3-request';
|
|
import { request as d3_request } from 'd3-request';
|
|
|
|
import { geoExtent } from '../geo';
|
|
import { impOsmError } from '../osm';
|
|
import { t } from '../util/locale';
|
|
import { utilRebind, utilTiler, utilQsString } from '../util';
|
|
|
|
|
|
var tiler = utilTiler();
|
|
var dispatch = d3_dispatch('loaded');
|
|
|
|
var _erCache;
|
|
var _erZoom = 14;
|
|
|
|
var _impOsmUrls = {
|
|
ow: 'http://directionofflow.skobbler.net/directionOfFlowService',
|
|
mr: 'http://missingroads.skobbler.net/missingGeoService',
|
|
tr: 'http://turnrestrictionservice.skobbler.net/turnRestrictionService'
|
|
};
|
|
|
|
var _missingTypes = {
|
|
PARKING: 'Unmapped parking',
|
|
ROAD: 'Unmapped road(s)',
|
|
BOTH: 'Unmapped road(s) and parking',
|
|
PATH: 'Unmapped path(s)',
|
|
WATER: 'Unmapped water feature' // ?
|
|
};
|
|
|
|
function abortRequest(i) {
|
|
_forEach(i, function(v) {
|
|
if (v) {
|
|
v.abort();
|
|
}
|
|
});
|
|
}
|
|
|
|
function abortUnwantedRequests(cache, tiles) {
|
|
_forEach(cache.inflight, function(v, k) {
|
|
var wanted = _find(tiles, function(tile) {
|
|
return k === tile.id;
|
|
});
|
|
if (!wanted) {
|
|
abortRequest(v);
|
|
delete cache.inflight[k];
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function encodeErrorRtree(d) {
|
|
return { minX: d.loc[0], minY: d.loc[1], maxX: d.loc[0], maxY: d.loc[1], data: d };
|
|
}
|
|
|
|
|
|
// replace or remove error from rtree
|
|
function updateRtree(item, replace) {
|
|
_erCache.rtree.remove(item, function isEql(a, b) {
|
|
return a.data.id === b.data.id;
|
|
});
|
|
|
|
if (replace) {
|
|
_erCache.rtree.insert(item);
|
|
}
|
|
}
|
|
|
|
export default {
|
|
init: function() {
|
|
if (!_erCache) {
|
|
this.reset();
|
|
}
|
|
|
|
this.event = utilRebind(this, dispatch, 'on');
|
|
},
|
|
|
|
reset: function() {
|
|
if (_erCache) {
|
|
_forEach(_erCache.inflight, abortRequest);
|
|
}
|
|
_erCache = {
|
|
data: {},
|
|
loaded: {},
|
|
inflight: {},
|
|
closed: {},
|
|
rtree: rbush()
|
|
};
|
|
},
|
|
|
|
loadErrors: function(projection) {
|
|
var options = {
|
|
client: 'iD',
|
|
confidenceLevel: 'C1', // most confident cases only for now
|
|
status: 'OPEN',
|
|
type: 'PARKING,ROAD,BOTH,PATH', // exclude WATER
|
|
zoom: '19'
|
|
};
|
|
|
|
// determine the needed tiles to cover the view
|
|
var tiles = tiler
|
|
.zoomExtent([_erZoom, _erZoom])
|
|
.getTiles(projection);
|
|
|
|
// abort inflight requests that are no longer needed
|
|
abortUnwantedRequests(_erCache, tiles);
|
|
|
|
// issue new requests..
|
|
tiles.forEach(function(tile) {
|
|
if (_erCache.loaded[tile.id] || _erCache.inflight[tile.id]) return;
|
|
|
|
var rect = tile.extent.rectangle();
|
|
var params = _extend({}, options, { east: rect[0], south: rect[3], west: rect[2], north: rect[1] });
|
|
|
|
// 3 separate requests to store for each tile
|
|
var requests = {};
|
|
|
|
// TODO: Just implement TRs and One-ways for now, much more simple
|
|
_forEach(_impOsmUrls, function(v, k) {
|
|
var url = v + '/search?' + utilQsString(params);
|
|
|
|
if (k == 'mr' || k == 'tr') return
|
|
|
|
requests[k] = d3_json(url,
|
|
function(err, data) {
|
|
delete _erCache.inflight[tile.id];
|
|
|
|
if (err) return;
|
|
_erCache.loaded[tile.id] = true;
|
|
|
|
// Clusters are returned at low zoom
|
|
// if (data.clusters) {
|
|
// data.clusters.forEach(function(feature) {
|
|
// var loc = feature.point;
|
|
// var size = feature.size;
|
|
// });
|
|
// }
|
|
|
|
// Road segments at high zoom == oneways
|
|
if (data.roadSegments) {
|
|
data.roadSegments.forEach(function(feature) {
|
|
var loc = feature.points[0];
|
|
|
|
var d = new impOsmError({
|
|
loc: [loc.lon, loc.lat],
|
|
comment: null,
|
|
description: '',
|
|
error_type: feature.type,
|
|
parent_error_type: k,
|
|
title: 'Missing One-way'
|
|
});
|
|
|
|
_erCache.data[d.id] = d;
|
|
_erCache.rtree.insert(encodeErrorRtree(d));
|
|
})
|
|
}
|
|
|
|
// Tiles at high zoom == missing roads
|
|
// if (data.tiles) {
|
|
// data.tiles.forEach(function(feature) {
|
|
// // Get description based on type
|
|
// var desc = _missingTypes[feature.type];
|
|
|
|
|
|
// var d = new impOsmError({
|
|
// loc: [feature.x, feature.y],
|
|
// comment: null,
|
|
// description: desc || '',
|
|
// error_type: feature.type,
|
|
// parent_error_type: k,
|
|
// title: 'Missing Roads'
|
|
// });
|
|
|
|
// _erCache.data[d.id] = d;
|
|
// _erCache.rtree.insert(encodeErrorRtree(d));
|
|
// })
|
|
// }
|
|
|
|
|
|
// if (data.entities) {
|
|
// data.entities.forEach(function(feature) {
|
|
// var loc = feature.point;
|
|
|
|
// var d = new impOsmError({
|
|
// loc: [loc.lat, loc.lon],
|
|
// comment: null,
|
|
// description: desc || '',
|
|
// error_type: feature.turnType,
|
|
// parent_error_type: k,
|
|
// title: 'Missing Turn Restriction'
|
|
// });
|
|
|
|
// _erCache.data[d.id] = d;
|
|
// _erCache.rtree.insert(encodeErrorRtree(d));
|
|
// })
|
|
// }
|
|
}
|
|
);
|
|
});
|
|
|
|
_erCache.inflight[tile.id] = requests;
|
|
dispatch.call('loaded');
|
|
})
|
|
},
|
|
|
|
// get all cached errors covering the viewport
|
|
getErrors: function(projection) {
|
|
var viewport = projection.clipExtent();
|
|
var min = [viewport[0][0], viewport[1][1]];
|
|
var max = [viewport[1][0], viewport[0][1]];
|
|
var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
|
|
|
|
return _erCache.rtree.search(bbox).map(function(d) {
|
|
return d.data;
|
|
});
|
|
},
|
|
|
|
// get a single error from the cache
|
|
getError: function(id) {
|
|
return _erCache.data[id];
|
|
},
|
|
|
|
// replace a single error in the cache
|
|
replaceError: function(error) {
|
|
if (!(error instanceof impOsmError) || !error.id) return;
|
|
|
|
_erCache.data[error.id] = error;
|
|
updateRtree(encodeErrorRtree(error), true); // true = replace
|
|
return error;
|
|
},
|
|
|
|
// remove a single error from the cache
|
|
removeError: function(error) {
|
|
if (!(error instanceof impOsmError) || !error.id) return;
|
|
|
|
delete _erCache.data[error.id];
|
|
updateRtree(encodeErrorRtree(error), false); // false = remove
|
|
}
|
|
};
|