Skip to content

Commit

Permalink
feat(plugins): readapt FeatureToolTip with pickFeaturesAt
Browse files Browse the repository at this point in the history
Since the last commit introducing pickFeaturesAt, this plugin has been
rewritten and documentation has been added.
  • Loading branch information
zarov committed Oct 4, 2019
1 parent 4032bc8 commit b037f3a
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 168 deletions.
4 changes: 2 additions & 2 deletions examples/effects_stereo.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,11 @@
debug.createTileDebugUI(menuGlobe.gui, view, view.tileLayer, d);

function updateScaleWidget() {
var value = view.controls.pixelsToMeters(200);
var value = view.getPixelsToMeters(200);
value = Math.floor(value);
var digit = Math.pow(10, value.toString().length - 1);
value = Math.round(value / digit) * digit;
var pix = view.controls.metersToPixels(value);
var pix = view.getMetersToPixels(value);
var unit = 'm';
if (value >= 1000) {
value /= 1000;
Expand Down
319 changes: 199 additions & 120 deletions examples/js/plugins/FeatureToolTip.js
Original file line number Diff line number Diff line change
@@ -1,150 +1,229 @@
/* global itowns */
/**
* A tooltip that can display some useful information about a feature when
* hovering it. Only works for layers using FileSource.
* hovering it.
*
* @param {View} viewer - The view to bind the tooltip to.
* @param {Object} options - Options to have more custom content displayed.
* @param {number} [options.precisionPx=5] - The precision of the picking.
* @param {function} [options.format] - A function that takes the name of the
* property currently being processed and its value, and gives the appropriate
* HTML output to it. If this method is specified, no others properties other
* than the ones handled in it will be displayed.
* @param {Array<string>} [options.filter] - An array of properties to filter.
* @param {boolean} [options.filterAll=true] - Filter all the properties.
* Default to true.
*
* @class FeatureToolTip
* @module FeatureToolTip
*
* @example
* view.addEventListener(itowns.VIEW_EVENTS.LAYERS_INITIALIZED, function() {
* new FeatureToolTip(view);
* });
* // Initialize the FeatureToolTip
* FeatureToolTip.init(viewerDiv, view);
*
* // Add layers
* var wfsSource = new itowns.WFSSource(...);
* var wfsLayer = new itowns.ColorLayer(wfsSource...);
* view.addLayer(wfsLayer);
*
* var fileSource = new itowns.FileSource(...);
* var fileLayer = new itowns.GeometryLayer(fileSource...);
* view.addLayer(fileLayer);
*
* FeatureToolTip.addLayer(wfsLayer);
* FeatureToolTip.addLayer(fileLayer);
*/
function FeatureToolTip(viewer, options) {
var opts = options || { filterAll: true };
opts.filter = opts.filter == undefined ? [] : opts.filter;

var tooltip = document.createElement('div');
tooltip.className = 'tooltip';
viewer.mainLoop.gfxEngine.renderer.domElement.parentElement.appendChild(tooltip);
var FeatureToolTip = (function _() {
var tooltip;
var view;
var layers = [];
var layersId = [];

var mouseDown = 0;
document.body.onmousedown = function onmousedown() {
document.body.addEventListener('mousedown', function _() {
++mouseDown;
};
document.body.onmouseup = function onmouseup() {
}, false);
document.body.addEventListener('mouseup', function _() {
--mouseDown;
};

var layer;
var result;
var feature;
var symb;
var style;
var fill;
var stroke;
var content;
var prop;
var layers = viewer.getLayers(function _(l) { return l.source && l.source.isFileSource; });
function buildToolTip(geoCoord, e) {
var visible = false;
var i = 0;
var p = 0;
var precision = viewer.controls.pixelsToDegrees(opts.precisionPx || 5);
}, false);

function moveToolTip(event) {
tooltip.innerHTML = '';
tooltip.style.display = 'none';
if (geoCoord) {
visible = false;

// Pick on each layer, and display them all in the tooltip
for (i = 0; i < layers.length; i++) {
layer = layers[i];

if (!layer.source.parsedData) { continue; }

result = itowns.FeaturesUtils.filterFeaturesUnderCoordinate(geoCoord, layer.source.parsedData, precision);

for (p = 0; p < result.length; p++) {
content = '';
visible = true;

feature = result[p].geometry;
style = layer.style.isStyle ? layer.style : feature.properties.style;
fill = style.fill.color;
stroke = '1.25px ' + style.stroke.color;

if (result[p].type === itowns.FEATURE_TYPES.POLYGON) {
symb = '&#9724';
} else if (result[p].type === itowns.FEATURE_TYPES.LINE) {
symb = '&#9473';
fill = style.stroke.color;
stroke = '0px';
} else if (result[p].type === itowns.FEATURE_TYPES.POINT) {
symb = '&#9679';
}

content += '<div>';
content += '<span style="color: ' + fill + '; -webkit-text-stroke: ' + stroke + '">';
content += symb + ' ';
content += '</span>';
content += (feature.properties.name || feature.properties.nom || feature.properties.description || layer.name);
var features = view.pickFeaturesAt(event, 3, layersId);

if (result[p].type === itowns.FEATURE_TYPES.POINT) {
content += '<br/><span class="coord">long ' + result[p].coordinates[0].toFixed(4) + '</span>';
content += '<br/><span class="coord">lat ' + result[p].coordinates[1].toFixed(4) + '</span>';
}
var layer;
for (var layerId in features) {
if (features[layerId].length == 0) {
continue;
}

layer = layers[layersId.indexOf(layerId)];
if (typeof layer.options.filterGeometries == 'function') {
features[layerId] = layer.options.filterGeometries(features[layerId], layer.layer) || [];
}
tooltip.innerHTML += fillToolTip(features[layerId], layer.layer, layer.options);
}

if (tooltip.innerHTML != '') {
tooltip.style.display = 'block';
tooltip.style.left = view.eventToViewCoords(event).x + 'px';
tooltip.style.top = view.eventToViewCoords(event).y + 'px';
}
}

function fillToolTip(features, layer, options) {
var content = '';
var feature;
var geometry;
var style;
var fill;
var stroke;
var symb = '';
var prop;

for (var p = 0; p < features.length; p++) {
feature = features[p];

geometry = feature.geometry;
style = (layer.style && layer.style.isStyle) ? layer.style : geometry.properties.style;
fill = style.fill.color;
stroke = '1.25px ' + style.stroke.color;

if (feature.type === itowns.FEATURE_TYPES.POLYGON) {
symb = '&#9724';
} else if (feature.type === itowns.FEATURE_TYPES.LINE) {
symb = '&#9473';
fill = style.stroke.color;
stroke = '0px';
} else if (feature.type === itowns.FEATURE_TYPES.POINT) {
symb = '&#9679';
}

if (feature.properties && !opts.filterAll) {
if (opts.format) {
for (prop in feature.properties) {
if (!opts.filter.includes(prop) && prop != 'style' && prop != 'name' && prop != 'nom' && prop != 'description') {
opts.format(prop, feature.properties[prop]);
}
}
} else {
content += '<ul>';
for (prop in feature.properties) {
if (!opts.filter.includes(prop) && prop != 'style' && prop != 'name' && prop != 'nom' && prop != 'description') {
content += '<li>' + prop + ': ' + feature.properties[prop] + '</li>';
}
}

if (content.endsWith('<ul>')) {
content = content.replace('<ul>', '');
} else {
content += '</ul>';
}
content += '<div>';
content += '<span style="color: ' + fill + '; -webkit-text-stroke: ' + stroke + '">';
content += symb + ' ';
content += '</span>';
content += (geometry.properties.name || geometry.properties.nom || geometry.properties.description || layer.name || '');

if (feature.type === itowns.FEATURE_TYPES.POINT) {
content += '<br/><span class="coord">long ' + feature.coordinates[0].toFixed(4) + '</span>';
content += '<br/><span class="coord">lat ' + feature.coordinates[1].toFixed(4) + '</span>';
}

if (geometry.properties && !options.filterAllProperties) {
if (options.format) {
for (prop in geometry.properties) {
if (!options.filterProperties.includes(prop)) {
content += options.format(prop, geometry.properties[prop]) || '';
}
}
} else {
content += '<ul>';
for (prop in geometry.properties) {
if (!options.filterProperties.includes(prop)) {
content += '<li>' + prop + ': ' + geometry.properties[prop] + '</li>';
}
}

content += '</div>';

tooltip.innerHTML += content;
if (content.endsWith('<ul>')) {
content = content.replace('<ul>', '');
} else {
content += '</ul>';
}
}
}

if (visible) {
tooltip.style.left = viewer.eventToViewCoords(e).x + 'px';
tooltip.style.top = viewer.eventToViewCoords(e).y + 'px';
tooltip.style.display = 'block';
}
content += '</div>';
}
}

function readPosition(e) {
if (!mouseDown) {
buildToolTip(viewer.controls.pickGeoPosition(viewer.eventToViewCoords(e)), e);
} else {
tooltip.style.left = viewer.eventToViewCoords(e).x + 'px';
tooltip.style.top = viewer.eventToViewCoords(e).y + 'px';
}
return content;
}

document.addEventListener('mousemove', readPosition, false);
document.addEventListener('mousedown', readPosition, false);
}
return {
/**
* Initialize the `FeatureToolTip` plugin for a specific view.
*
* @param {Element} viewerDiv - The element containing the viewer.
* @param {View} viewer - The view to bind the tooltip to.
*
* @example
* const viewerDiv = document.getElementById('viewerDiv');
* const view = new GlobeView(viewerDiv, { longitude: 4, latitude: 45, altitude: 3000 });
*
* FeatureToolTip.init(viewerDiv, view);
*
* @memberof module:FeatureToolTip
*/
init: function _(viewerDiv, viewer) {
// HTML element
tooltip = document.createElement('div');
tooltip.className = 'tooltip';
viewerDiv.appendChild(tooltip);

// View binding
view = viewer;

// Mouse movement listening
function onMouseMove(event) {
if (!mouseDown) {
moveToolTip(event);
} else {
tooltip.style.left = view.eventToViewCoords(event).x + 'px';
tooltip.style.top = view.eventToViewCoords(event).y + 'px';
}
}

document.addEventListener('mousemove', onMouseMove, false);
document.addEventListener('mousedown', onMouseMove, false);
},

/**
* Add a layer to be picked by the tooltip.
*
* @param {Layer} layer - The layer to add.
* @param {Object} options - Options to have more custom content displayed.
* @param {function} [options.filterGeometries] - A callback to filter
* geometries following a criteria, like an id found on FeatureGeometry
* properties. This is useful to remove duplicates, for example when a
* feature is present on multiple tiles at the same time (see the
* example below). This function takes two parameters: a list of
* features (usually a `Array<Feature>`) and the `Layer` associated to
* these features.
* @param {function} [options.format] - A function that takes the name
* of the property currently being processed and its value, and gives
* the appropriate HTML output to it. If this method is specified, no
* others properties other than the ones handled in it will be
* displayed.
* @param {Array<string>} [options.filterProperties] - An array of
* properties to filter.
* @param {boolean} [options.filterAllProperties=true] - Filter all the
* properties, and don't display anything besides the name of the layer
* the feature is attached to.
*
* @return {Layer} The added layer.
*
* @example
* FeatureToolTip.addLayer(wfsLayer, {
* filterProperties: ['uuid', 'notes', 'classification'],
* filterGeometries: (features, layer) => {
* const idList = [];
* return features.filter((f) => {
* if (!idList.includes(f.geometry.properties.id)) {
* idList.push(f.geometry.properties.id);
* return f;
* }
* });
* }
* });
*
* @memberof module:FeatureToolTip
*/
addLayer: function _(layer, options) {
if (!layer.isLayer) {
return layer;
}

var opts = options || { filterAllProperties: true };
opts.filterProperties = opts.filterProperties == undefined ? [] : opts.filterProperties;
opts.filterProperties.concat(['name', 'nom', 'style', 'description']);

layers.push({ layer: layer, options: opts });
layersId.push(layer.id);

return layer;
},
};
}());

if (typeof module != 'undefined' && module.exports) {
module.exports = FeatureToolTip;
Expand Down
8 changes: 5 additions & 3 deletions examples/plugins_vrt.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
// Instanciate iTowns GlobeView*
var view = new itowns.GlobeView(viewerDiv, positionOnGlobe);
var menuGlobe = new GuiTools('menuDiv', view);

setupLoadingScreen(viewerDiv, view);
FeatureToolTip.init(viewerDiv, view);

// Add one imagery layer to the scene
// This layer is defined in a json file but it could be defined as a plain js
Expand Down Expand Up @@ -62,9 +64,9 @@
}
}});

view.addLayer(velibLayer);

new FeatureToolTip(view, { filterAll: false });
return view.addLayer(velibLayer);
}).then(function _(layer) {
FeatureToolTip.addLayer(layer, { filterAllProperties: false });
});
</script>
</body>
Expand Down
Loading

0 comments on commit b037f3a

Please sign in to comment.