UNPKG

ol-cesium

Version:

OpenLayers Cesium integration library

1,024 lines 83.8 kB
import OLStyleIcon from 'ol/style/Icon.js'; import VectorSource, {} from 'ol/source/Vector.js'; import OLClusterSource from 'ol/source/Cluster.js'; import { circular as olCreateCircularPolygon } from 'ol/geom/Polygon.js'; import { boundingExtent, getCenter } from 'ol/extent.js'; import olGeomSimpleGeometry from 'ol/geom/SimpleGeometry.js'; import { convertColorToCesium, olGeometryCloneTo4326, ol4326CoordinateToCesiumCartesian, ol4326CoordinateArrayToCsCartesians } from './core'; import VectorLayerCounterpart, {} from './core/VectorLayerCounterpart'; import { getUid, waitReady } from './util'; import {} from 'ol/style/Style.js'; import { Geometry as OLGeometry } from 'ol/geom.js'; export default class FeatureConverter { scene; /** * Bind once to have a unique function for using as a listener */ boundOnRemoveOrClearFeatureListener_ = this.onRemoveOrClearFeature_.bind(this); defaultBillboardEyeOffset_ = new Cesium.Cartesian3(0, 0, 10); /** * Concrete base class for converting from OpenLayers3 vectors to Cesium * primitives. * Extending this class is possible provided that the extending class and * the library are compiled together by the closure compiler. * @param scene Cesium scene. * @api */ constructor(scene) { this.scene = scene; this.scene = scene; } /** * @param evt */ onRemoveOrClearFeature_(evt) { const source = evt.target; console.assert(source instanceof VectorSource); const cancellers = source['olcs_cancellers']; if (cancellers) { const feature = evt.feature; if (feature) { // remove const id = getUid(feature); const canceller = cancellers[id]; if (canceller) { canceller(); delete cancellers[id]; } } else { // clear for (const key in cancellers) { if (cancellers.hasOwnProperty(key)) { cancellers[key](); } } source['olcs_cancellers'] = {}; } } } /** * @param layer * @param feature OpenLayers feature. * @param primitive */ setReferenceForPicking(layer, feature, primitive) { primitive.olLayer = layer; primitive.olFeature = feature; } /** * Basics primitive creation using a color attribute. * Note that Cesium has 'interior' and outline geometries. * @param layer * @param feature OpenLayers feature. * @param olGeometry OpenLayers geometry. * @param geometry * @param color * @param opt_lineWidth * @return primitive */ createColoredPrimitive(layer, feature, olGeometry, geometry, color, opt_lineWidth) { const createInstance = function (geometry, color) { const instance = new Cesium.GeometryInstance({ geometry }); if (color && !(color instanceof Cesium.ImageMaterialProperty)) { instance.attributes = { color: Cesium.ColorGeometryInstanceAttribute.fromColor(color) }; } return instance; }; const options = { flat: true, // work with all geometries renderState: { depthTest: { enabled: true } } }; if (opt_lineWidth !== undefined) { options.renderState.lineWidth = opt_lineWidth; } const instances = createInstance(geometry, color); const heightReference = this.getHeightReference(layer, feature, olGeometry); let primitive; if (heightReference === Cesium.HeightReference.CLAMP_TO_GROUND) { if (!('createShadowVolume' in instances.geometry.constructor)) { // This is not a ground geometry return null; } primitive = new Cesium.GroundPrimitive({ geometryInstances: instances }); } else { primitive = new Cesium.Primitive({ geometryInstances: instances }); } if (color instanceof Cesium.ImageMaterialProperty) { // FIXME: we created stylings which are not time related // What should we pass here? // @ts-ignore const dataUri = color.image.getValue().toDataURL(); primitive.appearance = new Cesium.MaterialAppearance({ flat: true, renderState: { depthTest: { enabled: true, } }, material: new Cesium.Material({ fabric: { type: 'Image', uniforms: { image: dataUri } } }) }); } else { primitive.appearance = new Cesium.MaterialAppearance({ ...options, material: new Cesium.Material({ translucent: color.alpha !== 1, fabric: { type: 'Color', uniforms: { color, } } }) }); if (primitive instanceof Cesium.Primitive && (feature.get('olcs_shadows') || layer.get('olcs_shadows'))) { primitive.shadows = 1; } } this.setReferenceForPicking(layer, feature, primitive); return primitive; } /** * Return the fill or stroke color from a plain ol style. * @param style * @param outline * @return {!CSColor} */ extractColorFromOlStyle(style, outline) { const fillColor = style.getFill() ? style.getFill().getColor() : null; const strokeColor = style.getStroke() ? style.getStroke().getColor() : null; let olColor = 'black'; if (strokeColor && outline) { olColor = strokeColor; } else if (fillColor) { olColor = fillColor; } return convertColorToCesium(olColor); } /** * Return the width of stroke from a plain ol style. * @param style * @return {number} */ extractLineWidthFromOlStyle(style) { // Handling of line width WebGL limitations is handled by Cesium. const width = style.getStroke() ? style.getStroke().getWidth() : undefined; return width !== undefined ? width : 1; } /** * Create a primitive collection out of two Cesium geometries. * Only the OpenLayers style colors will be used. */ wrapFillAndOutlineGeometries(layer, feature, olGeometry, fillGeometry, outlineGeometry, olStyle) { const fillColor = this.extractColorFromOlStyle(olStyle, false); const outlineColor = this.extractColorFromOlStyle(olStyle, true); const primitives = new Cesium.PrimitiveCollection(); if (olStyle.getFill()) { const p1 = this.createColoredPrimitive(layer, feature, olGeometry, fillGeometry, fillColor); console.assert(!!p1); primitives.add(p1); } if (olStyle.getStroke() && outlineGeometry) { const width = this.extractLineWidthFromOlStyle(olStyle); const p2 = this.createColoredPrimitive(layer, feature, olGeometry, outlineGeometry, outlineColor, width); if (p2) { // Some outline geometries are not supported by Cesium in clamp to ground // mode. These primitives are skipped. primitives.add(p2); } } return primitives; } // Geometry converters // FIXME: would make more sense to only accept primitive collection. /** * Create a Cesium primitive if style has a text component. * Eventually return a PrimitiveCollection including current primitive. */ addTextStyle(layer, feature, geometry, style, primitive) { let primitives; if (!(primitive instanceof Cesium.PrimitiveCollection)) { primitives = new Cesium.PrimitiveCollection(); primitives.add(primitive); } else { primitives = primitive; } if (!style.getText()) { return primitives; } const text = /** @type {!ol.style.Text} */ (style.getText()); const label = this.olGeometry4326TextPartToCesium(layer, feature, geometry, text); if (label) { primitives.add(label); } return primitives; } /** * Add a billboard to a Cesium.BillboardCollection. * Overriding this wrapper allows manipulating the billboard options. * @param billboards * @param bbOptions * @param layer * @param feature OpenLayers feature. * @param geometry * @param style * @return newly created billboard * @api */ csAddBillboard(billboards, bbOptions, layer, feature, geometry, style) { if (!bbOptions.eyeOffset) { bbOptions.eyeOffset = this.defaultBillboardEyeOffset_; } const bb = billboards.add(bbOptions); this.setReferenceForPicking(layer, feature, bb); return bb; } /** * Convert an OpenLayers circle geometry to Cesium. * @api */ olCircleGeometryToCesium(layer, feature, olGeometry, projection, olStyle) { olGeometry = olGeometryCloneTo4326(olGeometry, projection); console.assert(olGeometry.getType() == 'Circle'); // ol.Coordinate const olCenter = olGeometry.getCenter(); const height = olCenter.length == 3 ? olCenter[2] : 0.0; const olPoint = olCenter.slice(); olPoint[0] += olGeometry.getRadius(); // Cesium const center = ol4326CoordinateToCesiumCartesian(olCenter); const point = ol4326CoordinateToCesiumCartesian(olPoint); // Accurate computation of straight distance const radius = Cesium.Cartesian3.distance(center, point); const fillGeometry = new Cesium.CircleGeometry({ center, radius, height }); let outlinePrimitive; let outlineGeometry; if (this.getHeightReference(layer, feature, olGeometry) === Cesium.HeightReference.CLAMP_TO_GROUND) { const width = this.extractLineWidthFromOlStyle(olStyle); if (width) { const circlePolygon = olCreateCircularPolygon(olGeometry.getCenter(), radius); const positions = ol4326CoordinateArrayToCsCartesians(circlePolygon.getLinearRing(0).getCoordinates()); const op = outlinePrimitive = new Cesium.GroundPolylinePrimitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: new Cesium.GroundPolylineGeometry({ positions, width }), }), appearance: new Cesium.PolylineMaterialAppearance({ material: this.olStyleToCesium(feature, olStyle, true), }), classificationType: Cesium.ClassificationType.TERRAIN, }); waitReady(outlinePrimitive).then(() => { this.setReferenceForPicking(layer, feature, op._primitive); }); } } else { outlineGeometry = new Cesium.CircleOutlineGeometry({ center, radius, extrudedHeight: height, height }); } const primitives = this.wrapFillAndOutlineGeometries(layer, feature, olGeometry, fillGeometry, outlineGeometry, olStyle); if (outlinePrimitive) { primitives.add(outlinePrimitive); } return this.addTextStyle(layer, feature, olGeometry, olStyle, primitives); } /** * Convert an OpenLayers line string geometry to Cesium. * @api */ olLineStringGeometryToCesium(layer, feature, olGeometry, projection, olStyle) { olGeometry = olGeometryCloneTo4326(olGeometry, projection); console.assert(olGeometry.getType() == 'LineString'); const positions = ol4326CoordinateArrayToCsCartesians(olGeometry.getCoordinates()); const width = this.extractLineWidthFromOlStyle(olStyle); let outlinePrimitive; const heightReference = this.getHeightReference(layer, feature, olGeometry); const appearance = new Cesium.PolylineMaterialAppearance({ material: this.olStyleToCesium(feature, olStyle, true) }); if (heightReference === Cesium.HeightReference.CLAMP_TO_GROUND) { const geometry = new Cesium.GroundPolylineGeometry({ positions, width, }); const op = outlinePrimitive = new Cesium.GroundPolylinePrimitive({ appearance, geometryInstances: new Cesium.GeometryInstance({ geometry }) }); waitReady(outlinePrimitive).then(() => { this.setReferenceForPicking(layer, feature, op._primitive); }); } else { const geometry = new Cesium.PolylineGeometry({ positions, width, vertexFormat: appearance.vertexFormat }); outlinePrimitive = new Cesium.Primitive({ appearance, geometryInstances: new Cesium.GeometryInstance({ geometry }), }); } this.setReferenceForPicking(layer, feature, outlinePrimitive); return this.addTextStyle(layer, feature, olGeometry, olStyle, outlinePrimitive); } /** * Convert an OpenLayers polygon geometry to Cesium. * @api */ olPolygonGeometryToCesium(layer, feature, olGeometry, projection, olStyle) { olGeometry = olGeometryCloneTo4326(olGeometry, projection); console.assert(olGeometry.getType() == 'Polygon'); const heightReference = this.getHeightReference(layer, feature, olGeometry); let fillGeometry, outlineGeometry; let outlinePrimitive; if ((olGeometry.getCoordinates()[0].length == 5) && (feature.get('olcs.polygon_kind') === 'rectangle')) { // Create a rectangle according to the longitude and latitude curves const coordinates = olGeometry.getCoordinates()[0]; // Extract the West, South, East, North coordinates const extent = boundingExtent(coordinates); const rectangle = Cesium.Rectangle.fromDegrees(extent[0], extent[1], extent[2], extent[3]); // Extract the average height of the vertices let maxHeight = 0.0; if (coordinates[0].length == 3) { for (let c = 0; c < coordinates.length; c++) { maxHeight = Math.max(maxHeight, coordinates[c][2]); } } const featureExtrudedHeight = feature.get('olcs_extruded_height'); // Render the cartographic rectangle fillGeometry = new Cesium.RectangleGeometry({ ellipsoid: Cesium.Ellipsoid.WGS84, rectangle, height: maxHeight, extrudedHeight: featureExtrudedHeight, }); outlineGeometry = new Cesium.RectangleOutlineGeometry({ ellipsoid: Cesium.Ellipsoid.WGS84, rectangle, height: maxHeight, extrudedHeight: featureExtrudedHeight, }); } else { const rings = olGeometry.getLinearRings(); const hierarchy = { positions: [], holes: [], }; const polygonHierarchy = hierarchy; console.assert(rings.length > 0); for (let i = 0; i < rings.length; ++i) { const olPos = rings[i].getCoordinates(); const positions = ol4326CoordinateArrayToCsCartesians(olPos); console.assert(positions && positions.length > 0); if (i === 0) { hierarchy.positions = positions; } else { hierarchy.holes.push({ positions, holes: [], }); } } const featureExtrudedHeight = feature.get('olcs_extruded_height'); fillGeometry = new Cesium.PolygonGeometry({ polygonHierarchy, perPositionHeight: true, extrudedHeight: featureExtrudedHeight, }); // Since Cesium doesn't yet support Polygon outlines on terrain yet (coming soon...?) // we don't create an outline geometry if clamped, but instead do the polyline method // for each ring. Most of this code should be removeable when Cesium adds // support for Polygon outlines on terrain. if (heightReference === Cesium.HeightReference.CLAMP_TO_GROUND) { const width = this.extractLineWidthFromOlStyle(olStyle); if (width > 0) { const positions = [hierarchy.positions]; if (hierarchy.holes) { for (let i = 0; i < hierarchy.holes.length; ++i) { positions.push(hierarchy.holes[i].positions); } } const appearance = new Cesium.PolylineMaterialAppearance({ material: this.olStyleToCesium(feature, olStyle, true) }); const geometryInstances = []; for (const linePositions of positions) { const polylineGeometry = new Cesium.GroundPolylineGeometry({ positions: linePositions, width }); geometryInstances.push(new Cesium.GeometryInstance({ geometry: polylineGeometry })); } outlinePrimitive = new Cesium.GroundPolylinePrimitive({ appearance, geometryInstances }); waitReady(outlinePrimitive).then(() => { this.setReferenceForPicking(layer, feature, outlinePrimitive._primitive); }); } } else { // Actually do the normal polygon thing. This should end the removable // section of code described above. outlineGeometry = new Cesium.PolygonOutlineGeometry({ polygonHierarchy: hierarchy, perPositionHeight: true, extrudedHeight: featureExtrudedHeight, }); } } const primitives = this.wrapFillAndOutlineGeometries(layer, feature, olGeometry, fillGeometry, outlineGeometry, olStyle); if (outlinePrimitive) { primitives.add(outlinePrimitive); } return this.addTextStyle(layer, feature, olGeometry, olStyle, primitives); } /** * @api */ getHeightReference(layer, feature, geometry) { // Read from the geometry let altitudeMode = geometry.get('altitudeMode'); // Or from the feature if (altitudeMode === undefined) { altitudeMode = feature.get('altitudeMode'); } // Or from the layer if (altitudeMode === undefined) { altitudeMode = layer.get('altitudeMode'); } let heightReference = Cesium.HeightReference.NONE; if (altitudeMode === 'clampToGround') { heightReference = Cesium.HeightReference.CLAMP_TO_GROUND; } else if (altitudeMode === 'relativeToGround') { heightReference = Cesium.HeightReference.RELATIVE_TO_GROUND; } return heightReference; } /** * Convert a point geometry to a Cesium BillboardCollection. * @param {ol.layer.Vector|ol.layer.Image} layer * @param {!ol.Feature} feature OpenLayers feature.. * @param {!ol.geom.Point} olGeometry OpenLayers point geometry. * @param {!ol.ProjectionLike} projection * @param {!ol.style.Style} style * @param {!ol.style.Image} imageStyle * @param {!Cesium.BillboardCollection} billboards * @param {function(!Cesium.Billboard)=} opt_newBillboardCallback Called when the new billboard is added. * @api */ createBillboardFromImage(layer, feature, olGeometry, projection, style, imageStyle, billboards, opt_newBillboardCallback) { if (imageStyle instanceof OLStyleIcon) { // make sure the image is scheduled for load imageStyle.load(); } const image = imageStyle.getImage(1); // get normal density const isImageLoaded = function (image) { return image.src != '' && image.naturalHeight != 0 && image.naturalWidth != 0 && image.complete; }; const reallyCreateBillboard = (function () { if (!image) { return; } if (!(image instanceof HTMLCanvasElement || image instanceof Image || image instanceof HTMLImageElement)) { return; } const center = olGeometry.getCoordinates(); const position = ol4326CoordinateToCesiumCartesian(center); let color; const opacity = imageStyle.getOpacity(); if (opacity !== undefined) { color = new Cesium.Color(1.0, 1.0, 1.0, opacity); } const scale = imageStyle.getScale(); const heightReference = this.getHeightReference(layer, feature, olGeometry); const bbOptions = { image, color, scale, heightReference, position }; // merge in cesium options from openlayers feature Object.assign(bbOptions, feature.get('cesiumOptions')); if (imageStyle instanceof OLStyleIcon) { const anchor = imageStyle.getAnchor(); if (anchor) { const xScale = (Array.isArray(scale) ? scale[0] : scale); const yScale = (Array.isArray(scale) ? scale[1] : scale); bbOptions.pixelOffset = new Cesium.Cartesian2((image.width / 2 - anchor[0]) * xScale, (image.height / 2 - anchor[1]) * yScale); } } const bb = this.csAddBillboard(billboards, bbOptions, layer, feature, olGeometry, style); if (opt_newBillboardCallback) { opt_newBillboardCallback(bb); } }).bind(this); if (image instanceof Image && !isImageLoaded(image)) { // Cesium requires the image to be loaded let cancelled = false; const source = layer.getSource(); const canceller = function () { cancelled = true; }; source.on(['removefeature', 'clear'], this.boundOnRemoveOrClearFeatureListener_); let cancellers = source['olcs_cancellers']; if (!cancellers) { cancellers = source['olcs_cancellers'] = {}; } const fuid = getUid(feature); if (cancellers[fuid]) { // When the feature change quickly, a canceller may still be present so // we cancel it here to prevent creation of a billboard. cancellers[fuid](); } cancellers[fuid] = canceller; const listener = function () { image.removeEventListener('load', listener); if (!billboards.isDestroyed() && !cancelled) { // Create billboard if the feature is still displayed on the map. reallyCreateBillboard(); } }; image.addEventListener('load', listener); } else { reallyCreateBillboard(); } } /** * Convert a point geometry to a Cesium BillboardCollection. * @param layer * @param feature OpenLayers feature.. * @param olGeometry OpenLayers point geometry. * @param projection * @param style * @param billboards * @param opt_newBillboardCallback Called when the new billboard is added. * @return primitives * @api */ olPointGeometryToCesium(layer, feature, olGeometry, projection, style, billboards, opt_newBillboardCallback) { console.assert(olGeometry.getType() == 'Point'); olGeometry = olGeometryCloneTo4326(olGeometry, projection); let modelPrimitive = null; const imageStyle = style.getImage(); if (imageStyle) { const olcsModelFunction = olGeometry.get('olcs_model') || feature.get('olcs_model'); if (olcsModelFunction) { modelPrimitive = new Cesium.PrimitiveCollection(); const olcsModel = olcsModelFunction(); const options = Object.assign({}, { scene: this.scene }, olcsModel.cesiumOptions); if ('fromGltf' in Cesium.Model) { // pre Cesium v107 // @ts-ignore const model = Cesium.Model.fromGltf(options); modelPrimitive.add(model); } else { Cesium.Model.fromGltfAsync(options).then((model) => { modelPrimitive.add(model); }); } if (olcsModel.debugModelMatrix) { modelPrimitive.add(new Cesium.DebugModelMatrixPrimitive({ modelMatrix: olcsModel.debugModelMatrix })); } } else { this.createBillboardFromImage(layer, feature, olGeometry, projection, style, imageStyle, billboards, opt_newBillboardCallback); } } if (style.getText()) { return this.addTextStyle(layer, feature, olGeometry, style, modelPrimitive || new Cesium.Primitive()); } else { return modelPrimitive; } } /** * Convert an OpenLayers multi-something geometry to Cesium. * @param {ol.layer.Vector|ol.layer.Image} layer * @param {!ol.Feature} feature OpenLayers feature.. * @param {!ol.geom.Geometry} geometry OpenLayers geometry. * @param {!ol.ProjectionLike} projection * @param {!ol.style.Style} olStyle * @param {!Cesium.BillboardCollection} billboards * @param {function(!Cesium.Billboard)=} opt_newBillboardCallback Called when * the new billboard is added. * @return {Cesium.Primitive} primitives * @api */ olMultiGeometryToCesium(layer, feature, geometry, projection, olStyle, billboards, opt_newBillboardCallback) { // Do not reproject to 4326 now because it will be done later. switch (geometry.getType()) { case 'MultiPoint': { const points = geometry.getPoints(); if (olStyle.getText()) { const primitives = new Cesium.PrimitiveCollection(); points.forEach((geom) => { console.assert(geom); const result = this.olPointGeometryToCesium(layer, feature, geom, projection, olStyle, billboards, opt_newBillboardCallback); if (result) { primitives.add(result); } }); return primitives; } else { points.forEach((geom) => { console.assert(geom); this.olPointGeometryToCesium(layer, feature, geom, projection, olStyle, billboards, opt_newBillboardCallback); }); return null; } } case 'MultiLineString': { const lineStrings = geometry.getLineStrings(); // FIXME: would be better to combine all child geometries in one primitive // instead we create n primitives for simplicity. const primitives = new Cesium.PrimitiveCollection(); lineStrings.forEach((geom) => { const p = this.olLineStringGeometryToCesium(layer, feature, geom, projection, olStyle); primitives.add(p); }); return primitives; } case 'MultiPolygon': { const polygons = geometry.getPolygons(); // FIXME: would be better to combine all child geometries in one primitive // instead we create n primitives for simplicity. const primitives = new Cesium.PrimitiveCollection(); polygons.forEach((geom) => { const p = this.olPolygonGeometryToCesium(layer, feature, geom, projection, olStyle); primitives.add(p); }); return primitives; } default: console.assert(false, `Unhandled multi geometry type${geometry.getType()}`); } } /** * Convert an OpenLayers text style to Cesium. * @api */ olGeometry4326TextPartToCesium(layer, feature, geometry, style) { const text = style.getText(); if (!text) { return null; } const labels = new Cesium.LabelCollection({ scene: this.scene }); // TODO: export and use the text draw position from OpenLayers . // See src/ol/render/vector.js const extentCenter = getCenter(geometry.getExtent()); if (geometry instanceof olGeomSimpleGeometry) { const first = geometry.getFirstCoordinate(); extentCenter[2] = first.length == 3 ? first[2] : 0.0; } const options = {}; options.position = ol4326CoordinateToCesiumCartesian(extentCenter); options.text = text; options.heightReference = this.getHeightReference(layer, feature, geometry); const offsetX = style.getOffsetX(); const offsetY = style.getOffsetY(); if (offsetX != 0 || offsetY != 0) { const offset = new Cesium.Cartesian2(offsetX, offsetY); options.pixelOffset = offset; } options.font = style.getFont() || '10px sans-serif'; // OpenLayers default let labelStyle = undefined; if (style.getFill()) { options.fillColor = this.extractColorFromOlStyle(style, false); labelStyle = Cesium.LabelStyle.FILL; } if (style.getStroke()) { options.outlineWidth = this.extractLineWidthFromOlStyle(style); options.outlineColor = this.extractColorFromOlStyle(style, true); labelStyle = Cesium.LabelStyle.OUTLINE; } if (style.getFill() && style.getStroke()) { labelStyle = Cesium.LabelStyle.FILL_AND_OUTLINE; } options.style = labelStyle; let horizontalOrigin; switch (style.getTextAlign()) { case 'left': horizontalOrigin = Cesium.HorizontalOrigin.LEFT; break; case 'right': horizontalOrigin = Cesium.HorizontalOrigin.RIGHT; break; case 'center': default: horizontalOrigin = Cesium.HorizontalOrigin.CENTER; } options.horizontalOrigin = horizontalOrigin; if (style.getTextBaseline()) { let verticalOrigin; switch (style.getTextBaseline()) { case 'top': verticalOrigin = Cesium.VerticalOrigin.TOP; break; case 'middle': verticalOrigin = Cesium.VerticalOrigin.CENTER; break; case 'bottom': verticalOrigin = Cesium.VerticalOrigin.BOTTOM; break; case 'alphabetic': verticalOrigin = Cesium.VerticalOrigin.TOP; break; case 'hanging': verticalOrigin = Cesium.VerticalOrigin.BOTTOM; break; default: console.assert(false, `unhandled baseline ${style.getTextBaseline()}`); } options.verticalOrigin = verticalOrigin; } const l = labels.add(options); this.setReferenceForPicking(layer, feature, l); return labels; } /** * Convert an OpenLayers style to a Cesium Material. * @api */ olStyleToCesium(feature, style, outline) { const fill = style.getFill(); const stroke = style.getStroke(); if ((outline && !stroke) || (!outline && !fill)) { return null; // FIXME use a default style? Developer error? } const olColor = outline ? stroke.getColor() : fill.getColor(); const color = convertColorToCesium(olColor); if (outline && stroke.getLineDash()) { return Cesium.Material.fromType('Stripe', { horizontal: false, repeat: 500, // TODO how to calculate this? evenColor: color, oddColor: new Cesium.Color(0, 0, 0, 0) // transparent }); } else { return Cesium.Material.fromType('Color', { color }); } } /** * Compute OpenLayers plain style. * Evaluates style function, blend arrays, get default style. * @api */ computePlainStyle(layer, feature, fallbackStyleFunction, resolution) { /** * @type {ol.FeatureStyleFunction|undefined} */ const featureStyleFunction = feature.getStyleFunction(); /** * @type {ol.style.Style|Array.<ol.style.Style>} */ let style = null; if (featureStyleFunction) { style = featureStyleFunction(feature, resolution); } if (!style && fallbackStyleFunction) { style = fallbackStyleFunction(feature, resolution); } if (!style) { // The feature must not be displayed return null; } // FIXME combine materials as in cesium-materials-pack? // then this function must return a custom material // More simply, could blend the colors like described in // http://en.wikipedia.org/wiki/Alpha_compositing return Array.isArray(style) ? style : [style]; } /** */ getGeometryFromFeature(feature, style, opt_geom) { if (opt_geom) { return opt_geom; } const geom3d = feature.get('olcs.3d_geometry'); if (geom3d && geom3d instanceof OLGeometry) { return geom3d; } if (style) { const geomFuncRes = style.getGeometryFunction()(feature); if (geomFuncRes instanceof OLGeometry) { return geomFuncRes; } } return feature.getGeometry(); } /** * Convert one OpenLayers feature up to a collection of Cesium primitives. * @api */ olFeatureToCesium(layer, feature, style, context, opt_geom) { const geom = this.getGeometryFromFeature(feature, style, opt_geom); if (!geom) { // OpenLayers features may not have a geometry // See http://geojson.org/geojson-spec.html#feature-objects return null; } const proj = context.projection; const newBillboardAddedCallback = function (bb) { const featureBb = context.featureToCesiumMap[getUid(feature)]; if (featureBb instanceof Array) { featureBb.push(bb); } else { context.featureToCesiumMap[getUid(feature)] = [bb]; } }; switch (geom.getType()) { case 'GeometryCollection': const primitives = new Cesium.PrimitiveCollection(); geom.getGeometriesArray().forEach((geom) => { if (geom) { const prims = this.olFeatureToCesium(layer, feature, style, context, geom); if (prims) { primitives.add(prims); } } }); return primitives; case 'Point': const bbs = context.billboards; const result = this.olPointGeometryToCesium(layer, feature, geom, proj, style, bbs, newBillboardAddedCallback); if (!result) { // no wrapping primitive return null; } else { return result; } case 'Circle': return this.olCircleGeometryToCesium(layer, feature, geom, proj, style); case 'LineString': return this.olLineStringGeometryToCesium(layer, feature, geom, proj, style); case 'Polygon': return this.olPolygonGeometryToCesium(layer, feature, geom, proj, style); case 'MultiPoint': return this.olMultiGeometryToCesium(layer, feature, geom, proj, style, context.billboards, newBillboardAddedCallback) || null; case 'MultiLineString': return this.olMultiGeometryToCesium(layer, feature, geom, proj, style, context.billboards, newBillboardAddedCallback) || null; case 'MultiPolygon': return this.olMultiGeometryToCesium(layer, feature, geom, proj, style, context.billboards, newBillboardAddedCallback) || null; case 'LinearRing': throw new Error('LinearRing should only be part of polygon.'); default: throw new Error(`Ol geom type not handled : ${geom.getType()}`); } } /** * Convert an OpenLayers vector layer to Cesium primitive collection. * For each feature, the associated primitive will be stored in * `featurePrimitiveMap`. * @api */ olVectorLayerToCesium(olLayer, olView, featurePrimitiveMap) { const proj = olView.getProjection(); const resolution = olView.getResolution(); if (resolution === undefined || !proj) { console.assert(false, 'View not ready'); // an assertion is not enough for closure to assume resolution and proj // are defined throw new Error('View not ready'); } let source = olLayer.getSource(); if (source instanceof OLClusterSource) { source = source.getSource(); } console.assert(source instanceof VectorSource); const features = source.getFeatures(); const counterpart = new VectorLayerCounterpart(proj, this.scene); const context = counterpart.context; for (let i = 0; i < features.length; ++i) { const feature = features[i]; if (!feature) { continue; } const layerStyle = olLayer.getStyleFunction(); const styles = this.computePlainStyle(olLayer, feature, layerStyle, resolution); if (!styles || !styles.length) { // only 'render' features with a style continue; } let primitives = null; for (let i = 0; i < styles.length; i++) { const prims = this.olFeatureToCesium(olLayer, feature, styles[i], context); if (prims) { if (!primitives) { primitives = prims; } else if (prims) { let i = 0, prim; while ((prim = prims.get(i))) { primitives.add(prim); i++; } } } } if (!primitives) { continue; } featurePrimitiveMap[getUid(feature)] = primitives; counterpart.getRootPrimitive().add(primitives); } return counterpart; } /** * Convert an OpenLayers feature to Cesium primitive collection. * @api */ convert(layer, view, feature, context) { const proj = view.getProjection(); const resolution = view.getResolution(); if (resolution == undefined || !proj) { return null; } /** * @type {ol.StyleFunction|undefined} */ const layerStyle = layer.getStyleFunction(); const styles = this.computePlainStyle(layer, feature, layerStyle, resolution); if (!styles || !styles.length) { // only 'render' features with a style return null; } context.projection = proj; /** * @type {Cesium.Primitive|null} */ let primitives = null; for (let i = 0; i < styles.length; i++) { const prims = this.olFeatureToCesium(layer, feature, styles[i], context); if (!primitives) { primitives = prims; } else if (prims) { let i = 0, prim; while ((prim = prims.get(i))) { primitives.add(prim); i++; } } } return primitives; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRmVhdHVyZUNvbnZlcnRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9vbGNzL0ZlYXR1cmVDb252ZXJ0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxXQUFXLE1BQU0sa0JBQWtCLENBQUM7QUFDM0MsT0FBTyxZQUFZLEVBQUUsRUFBd0IsTUFBTSxxQkFBcUIsQ0FBQztBQUN6RSxPQUFPLGVBQWUsTUFBTSxzQkFBc0IsQ0FBQztBQUNuRCxPQUFPLEVBQUMsUUFBUSxJQUFJLHVCQUF1QixFQUFDLE1BQU0sb0JBQW9CLENBQUM7QUFDdkUsT0FBTyxFQUFDLGNBQWMsRUFBRSxTQUFTLEVBQUMsTUFBTSxjQUFjLENBQUM7QUFDdkQsT0FBTyxvQkFBb0IsTUFBTSwyQkFBMkIsQ0FBQztBQUM3RCxPQUFPLEVBQUMsb0JBQW9CLEVBQUUscUJBQXFCLEVBQUUsaUNBQWlDLEVBQUUsbUNBQW1DLEVBQUMsTUFBTSxRQUFRLENBQUM7QUFDM0ksT0FBTyxzQkFBc0IsRUFBRSxFQUErQixNQUFNLCtCQUErQixDQUFDO0FBQ3BHLE9BQU8sRUFBQyxNQUFNLEVBQUUsU0FBUyxFQUFDLE1BQU0sUUFBUSxDQUFDO0FBTXpDLE9BQU8sRUFBMkMsTUFBTSxtQkFBbUIsQ0FBQztBQUk1RSxPQUFPLEVBQUMsUUFBUSxJQUFJLFVBQVUsRUFBNEksTUFBTSxZQUFZLENBQUM7QUFvRDdMLE1BQU0sQ0FBQyxPQUFPLE9BQU8sZ0JBQWdCO0lBaUJiO0lBZnRCOztPQUVHO0lBQ0ssb0NBQW9DLEdBQUcsSUFBSSxDQUFDLHVCQUF1QixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUUvRSwwQkFBMEIsR0FBRyxJQUFJLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUVyRTs7Ozs7OztPQU9HO0lBQ0gsWUFBc0IsS0FBWTtRQUFaLFVBQUssR0FBTCxLQUFLLENBQU87UUFDaEMs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