Good day everyone! I'm trying to render some sea ice data layers in openlayers. Most layers are no problem. But the layer that passes through Arcita is rendered very strangely. Arctic screen
I tried to process the antimeridian crossing. With one layer crossing the Arctic, this helped, and the other still looks like in the screenshot. Below is the code of my class, which is responsible for creating and adding a layer.
import LayerGroup from "ol/layer/Group";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import { Fill, Style } from "ol/style";
import GML_S411 from "../../GML-S411";
import * as olExtent from 'ol/extent';
class IceLayerWrapper {
#map;
#featureType;
#attribute;
#colors = {
iceact: {
fillColors: {
'1': '#0064ff',
'2': '#96c8ff',
'3': '#96c8ff',
'10': '#8cffa0',
'12': '#8cffa0',
'13': '#8cffa0',
'20': '#8cffa0',
'23': '#8cffa0',
'24': '#ffff00',
'30': '#8cffa0',
'34': '#ffff00',
'35': '#ffff00',
'40': '#ffff00',
'45': '#ffff00',
'46': '#ffff00',
'50': '#ffff00',
'56': '#ffff00',
'57': '#ff7d07',
'60': '#ffff00',
'67': '#ff7d07',
'68': '#ff7d07',
'70': '#ff7d07',
'78': '#ff7d07',
'79': '#ff0000',
'80': '#ff7d07',
'81': '#ff0000',
'89': '#ff0000',
'90': '#ff0000',
'91': '#ff0000',
'92': '#910000',
}
},
icesod: {
fillColors: {
'1': '#96c8ff',
'70': '#96c8ff',
'80': '#96c8ff',
'81': '#f0d2fa',
'82': '#ff64ff',
'83': '#aa28f0',
'84': '#873cd7',
'85': '#dc50eb',
'86': '#ffff00',
'87': '#9BD200',
'88': '#d7fa82',
'89': '#affa00',
'91': '#00c814',
'93': '#007800',
'94': '#007800',
'95': '#b46432',
'96': '#ff780a',
'97': '#c80000',
'98': '#ff0000',
},
}
}
constructor(options = {}) {
const { state, data, featureType, name, attribute, map } = options;
this.#map = map;
this.state = state;
this.#featureType = featureType;
this.data = data;
this.#attribute = attribute;
this.layersGroup = new LayerGroup({
opacity: state.opacity
});
this.name = name;
this.layersGroup.name = name;
this.layersGroup.setVisible(state.isShow);
}
format = () => new GML_S411({
featureNS: 'http://www.jcomm.info/ice',
featureType: this.#featureType,
});
#convertFeatures = features => {
const wrapWidth = olExtent.getWidth(this.#map.getView().getProjection().getExtent());
const wrapTransform = (input, opt_output, opt_dimension) => {
var length = input.length;
var dimension = opt_dimension !== undefined ? opt_dimension : 2;
var output = opt_output !== undefined ? opt_output : new Array(length);
var i, j;
for (i = 0; i < length; i += dimension) {
output[i] = input[i] < 0 ? input[i] + wrapWidth : input[i];
for (j = dimension - 1; j >= 1; --j) {
output[i + j] = input[i + j];
}
}
return output;
}
return features.map(feature => {
const geometry = feature.getGeometry();
// geometry.transform('EPSG:4326', 'EPSG:3857')
geometry.applyTransform(wrapTransform);
// feature.setGeometry(geometry);
return feature;
})
}
getFeatures = dataString => this.#convertFeatures(this.format()
.readFeatures(dataString, {
featureProjection: 'EPSG:3857',
dataProjection: 'EPSG:4326'
}));
createSource = features => new VectorSource({
features,
overlaps: false,
wrapX: false,
});
createLayer = (source, style) => new VectorLayer({
source,
style,
})
pushLayer = (layer) => {
const layers = this.layersGroup.getLayers();
layers.push(layer);
}
#getStyle = feature => {
const style = new Style();
let attributeValue = feature.values_[this.#attribute];
if (attributeValue) {
const splittedAttributeValue = attributeValue.split(';');
if (splittedAttributeValue) {
attributeValue = splittedAttributeValue[0];
}
const color = this.#colors[this.#attribute]?.fillColors[attributeValue];
if (color) {
const fill = new Fill({
color,
});
style.setFill(fill);
}
}
return style;
}
#handlerLayer = (layer, fileName) => {
const arr = fileName.split('_');
const temp = arr[arr.length - 2].toLowerCase();
layer.setVisible(this.state.layers[temp].isShow);
layer.name = temp;
}
#onFileHandler = fileData => {
const [fileName, dataString] = fileData;
const features = this
.getFeatures(dataString);
const source = this.createSource(features);
const layer = this.createLayer(source, this.#getStyle);
if (this.state.layers) {
this.#handlerLayer(layer, fileName);
}
this.pushLayer(layer);
}
addLayers = () => this.data.forEach(this.#onFileHandler);
init() {
this.addLayers();
return this.layersGroup;
}
}
export default IceLayerWrapper;
Thanks, for any help!
That can be a problem if the data is intended for display in a polar projection. To display in a global projection you will need to cut the polygons at the antimeridian. Transform the geometry to a polar projection and using turf.js take out a millimeter wide slice from near equator to the pole along the antimeridian (if displaying in a mercator projection where the pole is infinite, as close to the pole as possible). Then transform back to the display projection.
Code to convert features around the north pole
const format = new GeoJSON();
const splitter = format.writeFeatureObject(
new Feature(fromExtent([-1e-3, 1e2, 1e-3, 1e7]))
);
const newFeatures = [];
features.forEach(function (feature) {
newFeatures.push(
format.readFeature(
turf.difference(
format.writeFeatureObject(feature, {
dataProjection: 'EPSG:3995',
featureProjection: map.getView().getProjection(),
}),
splitter
),
{
dataProjection: 'EPSG:3995',
featureProjection: map.getView().getProjection(),
}
)
);
});
Working example https://codesandbox.io/s/simple-forked-vgt7fp?file=/main.js