terriajs
Version:
Geospatial data visualization platform.
390 lines (327 loc) • 15.1 kB
JavaScript
;
/*global require*/
var Color = require('terriajs-cesium/Source/Core/Color');
var defined = require('terriajs-cesium/Source/Core/defined');
var DeveloperError = require('terriajs-cesium/Source/Core/DeveloperError');
var Ellipsoid = require('terriajs-cesium/Source/Core/Ellipsoid');
var featureDataToGeoJson = require('../Map/featureDataToGeoJson');
var MapboxVectorTileImageryProvider = require('../Map/MapboxVectorTileImageryProvider');
var MapboxVectorCanvasTileLayer = require('../Map/MapboxVectorCanvasTileLayer');
var GeoJsonCatalogItem = require('./GeoJsonCatalogItem');
var Feature = require('./Feature');
var ImageryLayer = require('terriajs-cesium/Source/Scene/ImageryLayer');
var rectangleToLatLngBounds = require('../Map/rectangleToLatLngBounds');
require('./ImageryLayerFeatureInfo'); // overrides Cesium's prototype.configureDescriptionFromProperties
/**
* The base class for map/globe viewers.
*
* @constructor
* @alias GlobeOrMap
*
* @param {Terria} terria The Terria instance.
* @param {String} disclaimerClass Class of a disclaimer element that should be shifted upwards to make room for other ui elements.
*
* @see Cesium
* @see Leaflet
*/
var GlobeOrMap = function(terria) {
/**
* Gets or sets the Terria instance.
* @type {Terria}
*/
this.terria = terria;
/**
* Gets or sets whether this viewer _can_ show a splitter. Default false.
* @type {Boolean}
*/
this.canShowSplitter = false;
this._tilesLoadingCountMax = 0;
this._removeHighlightCallback = undefined;
this._highlightPromise = undefined;
};
GlobeOrMap._featureHighlightName = '___$FeatureHighlight&__';
/**
* Creates a {@see Feature} (based on an {@see Entity}) from a {@see ImageryLayerFeatureInfo}.
* @param {ImageryLayerFeatureInfo} imageryFeature The imagery layer feature for which to create an entity-based feature.
* @return {Feature} The created feature.
* @protected
*/
GlobeOrMap.prototype._createFeatureFromImageryLayerFeature = function(imageryFeature) {
var feature = new Feature({
id : imageryFeature.name,
});
feature.name = imageryFeature.name;
feature.description = imageryFeature.description; // already defined by the new Entity
feature.properties = imageryFeature.properties;
feature.data = imageryFeature.data;
feature.imageryLayer = imageryFeature.imageryLayer;
feature.position = Ellipsoid.WGS84.cartographicToCartesian(imageryFeature.position);
feature.coords = imageryFeature.coords;
return feature;
};
GlobeOrMap.prototype.updateTilesLoadingCount = function(tilesLoadingCount) {
if (tilesLoadingCount > this._tilesLoadingCountMax) {
this._tilesLoadingCountMax = tilesLoadingCount;
} else if (tilesLoadingCount === 0) {
this._tilesLoadingCountMax = 0;
}
this.terria.tileLoadProgressEvent.raiseEvent(tilesLoadingCount, this._tilesLoadingCountMax);
};
GlobeOrMap.prototype.isDestroyed = function() {
return false;
};
/**
* Picks features based off a latitude, longitude and (optionally) height.
* @param {Object} latlng The position on the earth to pick.
* @param {Object} imageryLayerCoords A map of imagery provider urls to the coords used to get features for those imagery
* providers - i.e. x, y, level
* @param existingFeatures An optional list of existing features to concatenate the ones found from asynchronous picking to.
*/
GlobeOrMap.prototype.pickFromLocation = function(latlng, imageryLayerCoords, existingFeatures) {
throw new DeveloperError('pickFromLocation must be implemented in the derived class.');
};
GlobeOrMap.prototype.destroy = function() {
throw new DeveloperError('destroy must be implemented in the derived class.');
};
/**
* Gets the current extent of the camera. This may be approximate if the viewer does not have a strictly rectangular view.
* @return {Rectangle} The current visible extent.
*/
GlobeOrMap.prototype.getCurrentExtent = function() {
throw new DeveloperError('getCurrentExtent must be implemented in the derived class.');
};
/**
* Gets the current container element.
* @return {Element} The current container element.
*/
GlobeOrMap.prototype.getContainer = function() {
throw new DeveloperError('getContainer must be implemented in the derived class.');
};
/**
* Zooms to a specified camera view or extent with a smooth flight animation.
*
* @param {CameraView|Rectangle} viewOrExtent The view or extent to which to zoom.
* @param {Number} [flightDurationSeconds=3.0] The length of the flight animation in seconds.
*/
GlobeOrMap.prototype.zoomTo = function(viewOrExtent, flightDurationSeconds) {
throw new DeveloperError('zoomTo must be implemented in the derived class.');
};
/**
* Captures a screenshot of the map.
* @return {Promise<string>} A promise that resolves to a data URL when the screenshot is ready.
*/
GlobeOrMap.prototype.captureScreenshot = function() {
throw new DeveloperError('captureScreenshot must be implemented in the derived class.');
};
/**
* Notifies the viewer that a repaint is required.
*/
GlobeOrMap.prototype.notifyRepaintRequired = function() {
throw new DeveloperError('notifyRepaintRequired must be implemented in the derived class.');
};
/**
* Computes the screen position of a given world position.
* @param {Cartesian3} position The world position in Earth-centered Fixed coordinates.
* @param {Cartesian2} [result] The instance to which to copy the result.
* @return {Cartesian2} The screen position, or undefined if the position is not on the screen.
*/
GlobeOrMap.prototype.computePositionOnScreen = function(position, result) {
throw new DeveloperError('computePositionOnScreen must be implemented in the derived class.');
};
/**
* Adds an attribution to the globe or map.
* @param {Credit} attribution The attribution to add.
*/
GlobeOrMap.prototype.addAttribution = function(attribution) {
throw new DeveloperError('addAttribution must be implemented in the derived class.');
};
/**
* Removes an attribution from the globe or map.
* @param {Credit} attribution The attribution to remove.
*/
GlobeOrMap.prototype.removeAttribution = function(attribution) {
throw new DeveloperError('removeAttribution must be implemented in the derived class.');
};
/**
* Gets all attribution currently active on the globe or map.
* @returns {String[]} The list of current attributions, as HTML strings.
*/
GlobeOrMap.prototype.getAllAttribution = function() {
return [];
};
/**
* Perform any updates to the order of layers required by raise and lower,
* but after the items have been reordered.
* This allows for the possibility that raise and lower do nothing, and instead we
* call updateLayerOrder
*/
GlobeOrMap.prototype.updateLayerOrderAfterReorder = function() {
throw new DeveloperError('updateLayerOrderAfterReorder must be implemented in the derived class.');
};
/**
* Raise an item's level in the viewer
* This does not check that index is valid
* @param {Number} index The index of the item to raise
*/
GlobeOrMap.prototype.raise = function(index) {
throw new DeveloperError('raise must be implemented in the derived class.');
};
/**
* Lower an item's level in the viewer
* This does not check that index is valid
* @param {Number} index The index of the item to lower
*/
GlobeOrMap.prototype.lower = function(index) {
throw new DeveloperError('lower must be implemented in the derived class.');
};
/**
* Lowers this imagery layer to the bottom, underneath all other layers. If this item is not enabled or not shown,
* this method does nothing.
* @param {CatalogItem} item The item to lower to the bottom (usually a basemap)
*/
GlobeOrMap.prototype.lowerToBottom = function(item) {
throw new DeveloperError('lowerToBottom must be implemented in the derived class.');
};
GlobeOrMap.prototype._highlightFeature = function(feature) {
if (defined(this._removeHighlightCallback)) {
this._removeHighlightCallback();
this._removeHighlightCallback = undefined;
this._highlightPromise = undefined;
}
if (defined(feature)) {
var hasGeometry = false;
if (defined(feature.polygon)) {
hasGeometry = true;
var polygonOutline = feature.polygon.outline;
var polygonOutlineColor = feature.polygon.outlineColor;
var polygonMaterial = feature.polygon.material;
feature.polygon.outline = true;
feature.polygon.outlineColor = Color.fromCssColorString(this.terria.baseMapContrastColor);
feature.polygon.material = Color.fromCssColorString(this.terria.baseMapContrastColor).withAlpha(0.75);
this._removeHighlightCallback = function() {
feature.polygon.outline = polygonOutline;
feature.polygon.outlineColor = polygonOutlineColor;
feature.polygon.material = polygonMaterial;
};
}
if (defined(feature.polyline)) {
hasGeometry = true;
var polylineMaterial = feature.polyline.material;
var polylineWidth = feature.polyline.width;
feature.polyline.material = Color.fromCssColorString(this.terria.baseMapContrastColor);
feature.polyline.width = 2;
this._removeHighlightCallback = function() {
feature.polyline.material = polylineMaterial;
feature.polyline.width = polylineWidth;
};
}
if (!hasGeometry) {
if (feature.imageryLayer && feature.imageryLayer.imageryProvider instanceof MapboxVectorTileImageryProvider) {
if (defined(this.terria.cesium)) {
var result = new ImageryLayer(feature.imageryLayer.imageryProvider.createHighlightImageryProvider(feature.data.id), {
show : true,
alpha : 1
});
var scene = this.terria.cesium.scene;
scene.imageryLayers.add(result);
this._removeHighlightCallback = function () {
scene.imageryLayers.remove(result);
};
} else if (defined(this.terria.leaflet)) {
var map = this.terria.leaflet.map;
var options = {
async: true,
opacity: 1,
bounds : rectangleToLatLngBounds(feature.imageryLayer.imageryProvider.rectangle),
};
if (defined(map.options.maxZoom)) {
options.maxZoom = map.options.maxZoom;
}
var layer = new MapboxVectorCanvasTileLayer(feature.imageryLayer.imageryProvider.createHighlightImageryProvider(feature.data.id), options);
layer.addTo(map);
layer.bringToFront();
this._removeHighlightCallback = function () {
map.removeLayer(layer);
};
}
} else {
var geoJson = featureDataToGeoJson(feature.data);
// Show geometry associated with the feature.
// Don't show points; the targeting cursor is sufficient.
if (geoJson && geoJson.geometry && geoJson.geometry.type !== 'Point') {
var catalogItem = new GeoJsonCatalogItem(this.terria);
catalogItem.name = GlobeOrMap._featureHighlightName;
catalogItem.data = geoJson;
catalogItem.style = {
'stroke-width': 2,
'stroke': this.terria.baseMapContrastColor,
'fill-opacity': 0,
'marker-color': this.terria.baseMapContrastColor
};
var that = this;
var removeCallback = this._removeHighlightCallback = function() {
that._highlightPromise.then(function() {
if (removeCallback !== that._removeHighlightCallback) {
return;
}
catalogItem._hide();
catalogItem._disable();
}).otherwise(function(){});
};
that._highlightPromise = catalogItem.load().then(function() {
if (removeCallback !== that._removeHighlightCallback) {
return;
}
catalogItem._enable();
catalogItem._show();
});
}
}
}
}
};
GlobeOrMap.prototype.addImageryProvider = function(options) {
throw new DeveloperError('addImageryProvider must be implemented in the derived class.');
};
GlobeOrMap.prototype.removeImageryLayer = function(options) {
throw new DeveloperError('removeImageryLayer must be implemented in the derived class.');
};
GlobeOrMap.prototype.showImageryLayer = function(options) {
throw new DeveloperError('showImageryLayer must be implemented in the derived class.');
};
GlobeOrMap.prototype.hideImageryLayer = function(options) {
throw new DeveloperError('hideImageryLayer must be implemented in the derived class.');
};
GlobeOrMap.prototype.isImageryLayerShown = function(options) {
throw new DeveloperError('isImageryLayerShown must be implemented in the derived class.');
};
GlobeOrMap.prototype.addDataSource = function(options) {
this.terria.dataSources.add(options.dataSource);
};
GlobeOrMap.prototype.removeDataSource = function(options) {
this.terria.dataSources.remove(options.dataSource, false);
};
GlobeOrMap.prototype.updateAllItemsForSplitter = function() {
this.terria.nowViewing.items.forEach(item => {
this.updateItemForSplitter(item);
});
this.notifyRepaintRequired();
};
GlobeOrMap.prototype.updateItemForSplitter = function(item) {
};
GlobeOrMap.prototype.pauseMapInteraction = function() {
};
GlobeOrMap.prototype.resumeMapInteraction = function() {
};
GlobeOrMap.disposeCommonListeners = function(globeOrMap) {
if (defined(globeOrMap._removeHighlightCallback)) {
globeOrMap._removeHighlightCallback();
globeOrMap._removeHighlightCallback = undefined;
globeOrMap._highlightPromise = undefined;
}
if (defined(globeOrMap._disclaimerShiftSubscription)) {
globeOrMap._disclaimerShiftSubscription.dispose();
globeOrMap._disclaimerShiftSubscription = undefined;
}
};
module.exports = GlobeOrMap;