ol-cesium
Version:
OpenLayers Cesium integration library
1,024 lines • 83.8 kB
JavaScript
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRmVhdHVyZUNvbnZlcnRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9vbGNzL0ZlYXR1cmVDb252ZXJ0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxXQUFXLE1BQU0sa0JBQWtCLENBQUM7QUFDM0MsT0FBTyxZQUFZLEVBQUUsRUFBd0IsTUFBTSxxQkFBcUIsQ0FBQztBQUN6RSxPQUFPLGVBQWUsTUFBTSxzQkFBc0IsQ0FBQztBQUNuRCxPQUFPLEVBQUMsUUFBUSxJQUFJLHVCQUF1QixFQUFDLE1BQU0sb0JBQW9CLENBQUM7QUFDdkUsT0FBTyxFQUFDLGNBQWMsRUFBRSxTQUFTLEVBQUMsTUFBTSxjQUFjLENBQUM7QUFDdkQsT0FBTyxvQkFBb0IsTUFBTSwyQkFBMkIsQ0FBQztBQUM3RCxPQUFPLEVBQUMsb0JBQW9CLEVBQUUscUJBQXFCLEVBQUUsaUNBQWlDLEVBQUUsbUNBQW1DLEVBQUMsTUFBTSxRQUFRLENBQUM7QUFDM0ksT0FBTyxzQkFBc0IsRUFBRSxFQUErQixNQUFNLCtCQUErQixDQUFDO0FBQ3BHLE9BQU8sRUFBQyxNQUFNLEVBQUUsU0FBUyxFQUFDLE1BQU0sUUFBUSxDQUFDO0FBTXpDLE9BQU8sRUFBMkMsTUFBTSxtQkFBbUIsQ0FBQztBQUk1RSxPQUFPLEVBQUMsUUFBUSxJQUFJLFVBQVUsRUFBNEksTUFBTSxZQUFZLENBQUM7QUFvRDdMLE1BQU0sQ0FBQyxPQUFPLE9BQU8sZ0JBQWdCO0lBaUJiO0lBZnRCOztPQUVHO0lBQ0ssb0NBQW9DLEdBQUcsSUFBSSxDQUFDLHVCQUF1QixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUUvRSwwQkFBMEIsR0FBRyxJQUFJLE1BQU0sQ0FBQyxVQUFVLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUVyRTs7Ozs7OztPQU9HO0lBQ0gsWUFBc0IsS0FBWTtRQUFaLFVBQUssR0FBTCxLQUFLLENBQU87UUFDaEMsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7SUFDckIsQ0FBQztJQUVEOztPQUVHO0lBQ0ssdUJBQXVCLENBQUMsR0FBc0I7UUFDcEQsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztRQUMxQixPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sWUFBWSxZQUFZLENBQUMsQ0FBQztRQUUvQyxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUM3QyxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQ2YsTUFBTSxPQUFPLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQztZQUM1QixJQUFJLE9BQU8sRUFBRSxDQUFDO2dCQUNaLFNBQVM7Z0JBQ1QsTUFBTSxFQUFFLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDO2dCQUMzQixNQUFNLFNBQVMsR0FBRyxVQUFVLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQ2pDLElBQUksU0FBUyxFQUFFLENBQUM7b0JBQ2QsU0FBUyxFQUFFLENBQUM7b0JBQ1osT0FBTyxVQUFVLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQ3hCLENBQUM7WUFDSCxDQUFDO2lCQUFNLENBQUM7Z0JBQ04sUUFBUTtnQkFDUixLQUFLLE1BQU0sR0FBRyxJQUFJLFVBQVUsRUFBRSxDQUFDO29CQUM3QixJQUFJLFVBQVUsQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQzt3QkFDbkMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7b0JBQ3BCLENBQUM7Z0JBQ0gsQ0FBQztnQkFDRCxNQUFNLENBQUMsaUJBQWlCLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDakMsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNPLHNCQUFzQixDQUFDLEtBQXFCLEVBQUUsT0FBZ0IsRUFBRSxTQUFpRjtRQUN6SixTQUFTLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztRQUMxQixTQUFTLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQztJQUNoQyxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNPLHNCQUFzQixDQUFDLEtBQXFCLEVBQUUsT0FBZ0IsRUFBRSxVQUFzQixFQUFFLFFBQXFDLEVBQUUsS0FBcUMsRUFBRSxhQUFzQjtRQUNwTSxNQUFNLGNBQWMsR0FBRyxVQUFTLFFBQXFDLEVBQUUsS0FBc0M7WUFDM0csTUFBTSxRQUFRLEdBQUcsSUFBSSxNQUFNLENBQUMsZ0JBQWdCLENBQUM7Z0JBQzNDLFFBQVE7YUFDVCxDQUFDLENBQUM7WUFDSCxJQUFJLEtBQUssSUFBSSxDQUFDLENBQUMsS0FBSyxZQUFZLE1BQU0sQ0FBQyxxQkFBcUIsQ0FBQyxFQUFFLENBQUM7Z0JBQzlELFFBQVEsQ0FBQyxVQUFVLEdBQUc7b0JBQ3BCLEtBQUssRUFBRSxNQUFNLENBQUMsOEJBQThCLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQztpQkFDOUQsQ0FBQztZQUNKLENBQUM7WUFDRCxPQUFPLFFBQVEsQ0FBQztRQUNsQixDQUFDLENBQUM7UUFFRixNQUFNLE9BQU8sR0FBOEI7WUFDekMsSUFBSSxFQUFFLElBQUksRUFBRSwyQkFBMkI7WUFDdkMsV0FBVyxFQUFFO2dCQUNYLFNBQVMsRUFBRTtvQkFDVCxPQUFPLEVBQUUsSUFBSTtpQkFDZDthQUNGO1NBQ0YsQ0FBQztRQUVGLElBQUksYUFBYSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ2hDLE9BQU8sQ0FBQyxXQUFXLENBQUMsU0FBUyxHQUFHLGFBQWEsQ0FBQztRQUNoRCxDQUFDO1FBRUQsTUFBTSxTQUFTLEdBQUcsY0FBYyxDQUFDLFFBQVEsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUVsRCxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsa0JBQWtCLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRSxVQUFVLENBQUMsQ0FBQztRQUU1RSxJQUFJLFNBQXNDLENBQUM7UUFFM0MsSUFBSSxlQUFlLEtBQUssTUFBTSxDQUFDLGVBQWUsQ0FBQyxlQUFlLEVBQUUsQ0FBQztZQUMvRCxJQUFJLENBQUMsQ0FBQyxvQkFBb0IsSUFBSSxTQUFTLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7Z0JBQzlELGdDQUFnQztnQkFDaEMsT0FBTyxJQUFJLENBQUM7WUFDZCxDQUFDO1lBQ0QsU0FBUyxHQUFHLElBQUksTUFBTSxDQUFDLGVBQWUsQ0FBQztnQkFDckMsaUJBQWlCLEVBQUUsU0FBUzthQUM3QixDQUFDLENBQUM7UUFDTCxDQUFDO2FBQU0sQ0FBQztZQUNOLFNBQVMsR0FBRyxJQUFJLE1BQU0sQ0FBQyxTQUFTLENBQUM7Z0JBQy9CLGlCQUFpQixFQUFFLFNBQVM7YUFDN0IsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUVELElBQUksS0FBSyxZQUFZLE1BQU0sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1lBQ2xELHdEQUF3RDtZQUN4RCw0QkFBNEI7WUFDNUIsYUFBYTtZQUNiLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsU0FBUyxFQUFFLENBQUM7WUFFbkQsU0FBUyxDQUFDLFVBQVUsR0FBRyxJQUFJLE1BQU0sQ0FBQyxrQkFBa0IsQ0FBQztnQkFDbkQsSUFBSSxFQUFFLElBQUk7Z0JBQ1YsV0FBVyxFQUFFO29CQUNYLFNBQVMsRUFBRTt3QkFDVCxPQUFPLEVBQUUsSUFBSTtxQkFDZDtpQkFDRjtnQkFDRCxRQUFRLEVBQUUsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDO29CQUM1QixNQUFNLEVBQUU7d0JBQ04sSUFBSSxFQUFFLE9BQU87d0JBQ2IsUUFBUSxFQUFFOzRCQUNSLEtBQUssRUFBRSxPQUFPO3lCQUNmO3FCQUNGO2lCQUNGLENBQUM7YUFDSCxDQUFDLENBQUM7UUFDTCxDQUFDO2FBQU0sQ0FBQztZQUNOLFNBQVMsQ0FBQyxVQUFVLEdBQUcsSUFBSSxNQUFNLENBQUMsa0JBQWtCLENBQUM7Z0JBQ25ELEdBQUcsT0FBTztnQkFDVixRQUFRLEVBQUUsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDO29CQUM1QixXQUFXLEVBQUUsS0FBSyxDQUFDLEtBQUssS0FBSyxDQUFDO29CQUM5QixNQUFNLEVBQUU7d0JBQ04sSUFBSSxFQUFFLE9BQU87d0JBQ2IsUUFBUSxFQUFFOzRCQUNSLEtBQUs7eUJBQ047cUJBQ0Y7aUJBQ0YsQ0FBQzthQUNILENBQUMsQ0FBQztZQUNILElBQUksU0FBUyxZQUFZLE1BQU0sQ0FBQyxTQUFTLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUN4RyxTQUFTLENBQUMsT0FBTyxHQUFHLENBQUMsQ0FBQztZQUN4QixDQUFDO1FBQ0gsQ0FBQztRQUNELElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1FBQ3ZELE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNPLHVCQUF1QixDQUFDLEtBQW1CLEVBQUUsT0FBZ0I7UUFDckUsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztRQUN0RSxNQUFNLFdBQVcsR0FBRyxLQUFLLENBQUMsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBRTVFLElBQUksT0FBTyxHQUEwQixPQUFPLENBQUM7UUFDN0MsSUFBSSxXQUFXLElBQUksT0FBTyxFQUFFLENBQUM7WUFDM0IsT0FBTyxHQUFHLFdBQVcsQ0FBQztRQUN4QixDQUFDO2FBQU0sSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUNyQixPQUFPLEdBQUcsU0FBUyxDQUFDO1FBQ3RCLENBQUM7UUFFRCxPQUFPLG9CQUFvQixDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3ZDLENBQUM7SUFFRDs7OztPQUlHO0lBQ08sMkJBQTJCLENBQUMsS0FBbUI7UUFDdkQsaUVBQWlFO1FBQ2pFLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7UUFDM0UsT0FBTyxLQUFLLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN6QyxDQUFDO0lBRUQ7OztPQUdHO0lBQ08sNEJBQTRCLENBQUMsS0FBcUIsRUFBRSxPQUFnQixFQUFFLFVBQXNCLEVBQUUsWUFBeUMsRUFBRSxlQUFtRCxFQUFFLE9BQWM7UUFDcE4sTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLHVCQUF1QixDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMvRCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsdUJBQXVCLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO1FBRWpFLE1BQU0sVUFBVSxHQUFHLElBQUksTUFBTSxDQUFDLG1CQUFtQixFQUFFLENBQUM7UUFDcEQsSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztZQUN0QixNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsc0JBQXNCLENBQUMsS0FBSyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQzdELFlBQVksRUFBRSxTQUFTLENBQUMsQ0FBQztZQUM3QixPQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNyQixVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ3JCLENBQUM7UUFFRCxJQUFJLE9BQU8sQ0FBQyxTQUFTLEVBQUUsSUFBSSxlQUFlLEVBQUUsQ0FBQztZQUMzQyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsMkJBQTJCLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDeEQsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDLHNCQUFzQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUM3RCxlQUFlLEVBQUUsWUFBWSxFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQzFDLElBQUksRUFBRSxFQUFFLENBQUM7Z0JBQ1AseUVBQXlFO2dCQUN6RSxzQ0FBc0M7Z0JBQ3RDLFVBQVUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDckIsQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPLFVBQVUsQ0FBQztJQUNwQixDQUFDO0lBRUQsc0JBQXNCO0lBRXRCLG9FQUFvRTtJQUNwRTs7O09BR0c7SUFDTyxZQUFZLENBQUMsS0FBcUIsRUFBRSxPQUFnQixFQUFFLFFBQW9CLEVBQUUsS0FBWSxFQUFFLFNBQW9FO1FBQ3RLLElBQUksVUFBVSxDQUFDO1FBQ2YsSUFBSSxDQUFDLENBQUMsU0FBUyxZQUFZLE1BQU0s