Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "mobility-toolbox-js",
"license": "MIT",
"description": "Toolbox for JavaScript applications in the domains of mobility and logistics.",
"version": "3.4.6",
"version": "3.5.0-beta.0",
"homepage": "https://mobility-toolbox-js.geops.io/",
"exports": {
".": "./index.js",
Expand Down
121 changes: 91 additions & 30 deletions src/ol/utils/MapsetKmlFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ interface IconOptions {
zIndex: number;
}

interface ScaleOptions {
defaultScale?: number;
resolution: number;
}

// Comes from ol >= 6.7,
// https://github.com/openlayers/openlayers/blob/main/src/ol/format/KML.js#L320
const scaleForSize = (size: Size) => {
Expand Down Expand Up @@ -137,7 +142,62 @@ const getLineIcon = (
});
};

type zoomLimitsType = null | number[][];

function validateZoomLimits(zoomLimits: zoomLimitsType): boolean {
return (
Array.isArray(zoomLimits) &&
zoomLimits.every((item) => {
return (
Array.isArray(item) &&
item.length === 2 &&
item.every((n) => {
return typeof n === 'number' && !Number.isNaN(n);
})
);
})
);
}

class MapsetKmlFormat {
/**
* Get Document properties.
* @param {String} kmlString A string representing a KML file.
* @returns {Object} An object containing the document properties (name, description, zoomLimits).
*/
public getDocumentProperties(
Copy link
Collaborator

@oterral oterral Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you never use this method, so if it is a utility method for Mapset . Creates someting more generic, so we could use it like this , when we need it (with good type for the parameter):

getDocumentProperty("name")
getDocumentProperty("description")
getDocumentProperty("zoomLimits")

it will be easier to maintain if we add more properties.

kmlString: string,
): Record<string, number | number[] | object | string | zoomLimitsType> {
const kmlDoc = parse(kmlString);
let documentProperties: Record<
string,
number | object | string | zoomLimitsType
> = {};
const documentNode = kmlDoc.getElementsByTagName('Document')[0];
if (documentNode) {
const name = documentNode.getElementsByTagName('name')[0]?.textContent;
const description =
documentNode.getElementsByTagName('description')[0]?.textContent;
const zoomLimitsData = documentNode
.getElementsByTagName('ExtendedData')[0]
?.getElementsByTagName('Data')[0];
let zoomLimits: null | number[][] = null;
if (zoomLimitsData?.getAttribute('name') === 'zoomLimits') {
const value =
zoomLimitsData.getElementsByTagName('value')[0]?.textContent;
try {
zoomLimits = JSON.parse(value || '') as number[][];
if (!validateZoomLimits(zoomLimits)) {
zoomLimits = null;
}
} catch {
zoomLimits = null;
}
}
documentProperties = { description, name, zoomLimits };
}
return documentProperties;
}
/**
* Read a KML string.
* @param {String} kmlString A string representing a KML file.
Expand Down Expand Up @@ -168,7 +228,7 @@ class MapsetKmlFormat {
transform(
JSON.parse(circleGeometryCenter as string) as Coordinate,
EPSG_4326,
featureProjection || EPSG_4326,
featureProjection ?? EPSG_4326,
),
parseFloat(circleGeometryRadius as string),
);
Expand Down Expand Up @@ -259,6 +319,13 @@ class MapsetKmlFormat {
style?.setZIndex(parseInt(feature.get('zIndex') as string, 10));
}

const scaleOptions = (feature.get('pictureOptions') ??
feature.get('scaleOptions')) as ScaleOptions | string;
if (scaleOptions && typeof scaleOptions === 'string') {
const parsed = JSON.parse(scaleOptions) as ScaleOptions;
feature.set('scaleOptions', parsed);
}

// if the feature is a Point and we are offline, we use default vector
// style.
// if the feature is a Point and has a name with a text style, we
Expand Down Expand Up @@ -409,32 +476,18 @@ class MapsetKmlFormat {
stroke = null;

styles = (feat, resolution) => {
/* Options to be used for picture scaling with map, should have at least
/* Options to be used for feature scaling with map, should have at least
* a resolution attribute (this is the map resolution at the zoom level when
* the picture is created), can take an optional constant for further scale
* the feature is created), can take an optional constant for further scale
* adjustment.
* e.g. { resolution: 0.123, defaultScale: 1 / 6 }
*/

interface PictureOptions {
defaultScale?: number;
resolution: number;
}

if (feat.get('pictureOptions')) {
let pictureOptions = feat.get('pictureOptions') as
| PictureOptions
| string;
if (typeof pictureOptions === 'string') {
pictureOptions = JSON.parse(pictureOptions) as PictureOptions;
}
(feat as FeatureType).set('pictureOptions', pictureOptions);
if (pictureOptions.resolution) {
image?.setScale(
(pictureOptions.resolution / resolution) *
(pictureOptions?.defaultScale ?? 1),
);
}
const scaleOpts = feat.get('scaleOptions') as ScaleOptions;
if (scaleOpts && image instanceof Icon && scaleOpts.resolution) {
image.setScale(
(scaleOpts.resolution / resolution) * (scaleOpts.defaultScale ?? 1),
);
}

return new Style({
Expand Down Expand Up @@ -742,14 +795,6 @@ class MapsetKmlFormat {
// We set the scale as extended metadata because the <scale> in the KML is related to a 32px img, since ol >= 6.10.
clone.set('iconScale', newStyle.image.getScale());
}

// Set map resolution to use for icon-to-map proportional scaling
if (feature.get('pictureOptions')) {
clone.set(
'pictureOptions',
JSON.stringify(feature.get('pictureOptions')),
);
}
}

// In case a fill pattern should be applied (use fillPattern attribute to store pattern id, color etc)
Expand All @@ -768,6 +813,14 @@ class MapsetKmlFormat {
clone.set('minZoom', parseFloat(feature.get('minZoom') as string));
}

// Set map resolution to use for feature-to-map proportional scaling
if (feature.get('pictureOptions') || feature.get('scaleOptions')) {
clone.set(
'scaleOptions',
JSON.stringify(feature.get('scaleOptions')),
);
}

// If only text is displayed we must specify an
// image style with scale=0
if (newStyle.text && !newStyle.image) {
Expand Down Expand Up @@ -858,6 +911,14 @@ class MapsetKmlFormat {
// <Document> tag
featString = featString.replace(/<Placemark\/>/g, '');

// Add KML document zoomLimits
if (layer.get('zoomLimits')) {
featString = featString.replace(
/<Document>/,
`<Document><ExtendedData><Data name="zoomLimits"><value>${JSON.stringify(layer.get('zoomLimits'))}</value></Data></ExtendedData>`,
);
}

// Add KML document name
if (layer.get('name')) {
featString = featString.replace(
Expand Down
Loading