Skip to content

Commit 97101b3

Browse files
committed
fix(otef-projection): fixes marker size and outline not correct; added name label scale for realtime size adj
1 parent d1f208c commit 97101b3

5 files changed

Lines changed: 167 additions & 29 deletions

File tree

otef-interactive/frontend/js/map-utils/advanced-style-drawing.js

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ class AdvancedStyleDrawing {
128128
let radius = 5;
129129
let hatch = null;
130130
let dashArray = null;
131+
let strokeApplied = false;
131132

132133
for (const layer of symbolLayers) {
133134
if (layer.type === "fill") {
@@ -146,20 +147,39 @@ class AdvancedStyleDrawing {
146147
};
147148
}
148149
} else if (layer.type === "stroke") {
149-
strokeColor = layer.color || strokeColor;
150-
strokeOpacity =
151-
layer.opacity !== undefined ? layer.opacity : strokeOpacity;
152-
lineWidth = layer.width !== undefined ? layer.width : lineWidth;
153-
if (layer.dash && Array.isArray(layer.dash.array)) {
154-
dashArray = layer.dash.array.slice();
150+
const opacity =
151+
layer.opacity !== undefined ? layer.opacity : 1.0;
152+
const width = layer.width !== undefined ? layer.width : 1;
153+
const visible = opacity > 0 && width > 0;
154+
if (visible && !strokeApplied) {
155+
strokeColor = layer.color || strokeColor;
156+
strokeOpacity = opacity;
157+
lineWidth = width;
158+
strokeApplied = true;
159+
if (layer.dash && Array.isArray(layer.dash.array)) {
160+
dashArray = layer.dash.array.slice();
161+
}
162+
} else if (!strokeApplied) {
163+
strokeColor = layer.color || strokeColor;
164+
strokeOpacity = opacity;
165+
lineWidth = width;
166+
if (layer.dash && Array.isArray(layer.dash.array)) {
167+
dashArray = layer.dash.array.slice();
168+
}
155169
}
156170
} else if (layer.type === "markerPoint") {
157-
radius =
171+
// marker.size from styles.json is diameter (px); use half for circle radius
172+
const sizePx =
158173
layer.marker && typeof layer.marker.size === "number"
159174
? layer.marker.size
160-
: radius;
175+
: radius * 2;
176+
radius = sizePx / 2;
161177
}
162178
}
179+
if (!strokeApplied && (baseFillColor || hatch)) {
180+
strokeOpacity = 1.0;
181+
lineWidth = lineWidth > 0 ? lineWidth : 1;
182+
}
163183

164184
const fillColor = baseFillColor || "#808080";
165185
const fillOpacity = baseFillColor ? baseFillOpacity : 0;
@@ -190,6 +210,8 @@ class AdvancedStyleDrawing {
190210
let strokeOpacity = 1.0;
191211
let lineWidth = 1;
192212
let radius = 5;
213+
let markerStrokeFromPoint = null;
214+
let strokeApplied = false;
193215

194216
for (const layer of symbolLayers) {
195217
if (layer.type === "fill") {
@@ -198,20 +220,45 @@ class AdvancedStyleDrawing {
198220
fillOpacity = layer.opacity;
199221
}
200222
} else if (layer.type === "stroke") {
201-
strokeColor = layer.color || strokeColor;
202-
if (layer.opacity !== undefined) {
203-
strokeOpacity = layer.opacity;
204-
}
205-
if (layer.width !== undefined) {
206-
lineWidth = layer.width;
223+
const opacity =
224+
layer.opacity !== undefined ? layer.opacity : 1.0;
225+
const width = layer.width !== undefined ? layer.width : 1;
226+
const visible = opacity > 0 && width > 0;
227+
if (visible && !strokeApplied) {
228+
strokeColor = layer.color || strokeColor;
229+
strokeOpacity = opacity;
230+
lineWidth = width;
231+
strokeApplied = true;
232+
} else if (!strokeApplied) {
233+
strokeColor = layer.color || strokeColor;
234+
strokeOpacity = opacity;
235+
lineWidth = width;
207236
}
208-
} else if (layer.type === "markerPoint") {
209-
radius =
210-
layer.marker && typeof layer.marker.size === "number"
237+
} else if (layer.type === "markerPoint" && layer.marker) {
238+
const sizePx =
239+
typeof layer.marker.size === "number"
211240
? layer.marker.size
212-
: radius;
241+
: radius * 2;
242+
radius = sizePx / 2;
243+
if (
244+
layer.marker.strokeColor != null ||
245+
(typeof layer.marker.strokeWidth === "number" && layer.marker.strokeWidth > 0)
246+
) {
247+
markerStrokeFromPoint = {
248+
color: layer.marker.strokeColor || "#000000",
249+
width:
250+
typeof layer.marker.strokeWidth === "number" && layer.marker.strokeWidth > 0
251+
? layer.marker.strokeWidth
252+
: 1,
253+
};
254+
}
213255
}
214256
}
257+
if (markerStrokeFromPoint) {
258+
strokeColor = markerStrokeFromPoint.color;
259+
lineWidth = markerStrokeFromPoint.width;
260+
strokeOpacity = 1.0;
261+
}
215262

216263
const styleForDraw = { hatch: null, dashArray: null };
217264

otef-interactive/frontend/js/map-utils/style-applicator.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ class StyleApplicator {
4949
fillOpacity:
5050
defaultStyle.fillOpacity !== undefined ? defaultStyle.fillOpacity : 0.7,
5151
color: defaultStyle.strokeColor || "#333333",
52-
weight: (defaultStyle.strokeWidth || 0.5) * this.PT_TO_PX,
52+
// strokeWidth in styles.json is already in CSS pixels
53+
weight: defaultStyle.strokeWidth || 0.5,
5354
opacity:
5455
defaultStyle.strokeOpacity !== undefined
5556
? defaultStyle.strokeOpacity
@@ -73,7 +74,8 @@ class StyleApplicator {
7374
? defaultStyle.fillOpacity
7475
: 0.7,
7576
color: defaultStyle.strokeColor || "#000000",
76-
weight: (defaultStyle.strokeWidth || 1.0) * this.PT_TO_PX,
77+
// strokeWidth in styles.json is already in CSS pixels
78+
weight: defaultStyle.strokeWidth || 1.0,
7779
opacity:
7880
defaultStyle.strokeOpacity !== undefined
7981
? defaultStyle.strokeOpacity
@@ -138,14 +140,14 @@ class StyleApplicator {
138140

139141
if (valueStyle) {
140142
const weightPx =
141-
(valueStyle.strokeWidth !== undefined
143+
valueStyle.strokeWidth !== undefined
142144
? valueStyle.strokeWidth
143145
: defaultStyle.strokeWidth !== undefined
144146
? defaultStyle.strokeWidth
145-
: 1.0) * this.PT_TO_PX;
147+
: 1.0;
146148

147-
const rawRadiusPx =
148-
(valueStyle.radius || defaultStyle.radius || 5) * this.PT_TO_PX;
149+
// radius values from styles.json are already in CSS pixels
150+
const rawRadiusPx = valueStyle.radius || defaultStyle.radius || 5;
149151
const radiusPx = clampRadiusIfNeeded(rawRadiusPx);
150152

151153
return {
@@ -174,7 +176,7 @@ class StyleApplicator {
174176

175177
// Fallback to default style
176178
const fallbackRadiusPx = clampRadiusIfNeeded(
177-
(defaultStyle.radius || 5) * this.PT_TO_PX,
179+
defaultStyle.radius || 5,
178180
);
179181

180182
return {
@@ -184,7 +186,7 @@ class StyleApplicator {
184186
? defaultStyle.fillOpacity
185187
: 0.7,
186188
color: defaultStyle.strokeColor || "#000000",
187-
weight: (defaultStyle.strokeWidth || 1.0) * this.PT_TO_PX,
189+
weight: defaultStyle.strokeWidth || 1.0,
188190
opacity:
189191
defaultStyle.strokeOpacity !== undefined
190192
? defaultStyle.strokeOpacity
@@ -230,12 +232,13 @@ class StyleApplicator {
230232
const defaultStyle = style.defaultStyle || {};
231233

232234
return {
233-
radius: (defaultStyle.radius || 5) * this.PT_TO_PX,
235+
// radius and strokeWidth in styles.json are already in CSS pixels
236+
radius: defaultStyle.radius || 5,
234237
fillColor: defaultStyle.fillColor || "#808080",
235238
fillOpacity:
236239
defaultStyle.fillOpacity !== undefined ? defaultStyle.fillOpacity : 0.7,
237240
color: defaultStyle.strokeColor || "#000000",
238-
weight: (defaultStyle.strokeWidth || 1.0) * this.PT_TO_PX,
241+
weight: defaultStyle.strokeWidth || 1.0,
239242
opacity:
240243
defaultStyle.strokeOpacity !== undefined
241244
? defaultStyle.strokeOpacity

otef-interactive/frontend/js/projection/layer-renderer-canvas.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,17 @@ class CanvasLayerRenderer {
318318
const field = labels.field || "name";
319319
const fontFamily = labels.font || "Arial, sans-serif";
320320
const sizePt = typeof labels.size === "number" ? labels.size : 10;
321-
const sizePx = Math.max(8, (sizePt * 96) / 72);
321+
// Convert ArcGIS point size to CSS pixels, then apply optional projector-wide scale factor.
322+
let sizePx = (sizePt * 96) / 72;
323+
let labelScale = 1;
324+
if (
325+
typeof MapProjectionConfig !== "undefined" &&
326+
MapProjectionConfig &&
327+
typeof MapProjectionConfig.LABEL_SIZE_SCALE === "number"
328+
) {
329+
labelScale = MapProjectionConfig.LABEL_SIZE_SCALE;
330+
}
331+
sizePx = Math.max(8, sizePx * labelScale);
322332
const color = labels.color || "#000000";
323333
const opacity = labels.colorOpacity != null ? labels.colorOpacity : 1;
324334
const haloSize = typeof labels.haloSize === "number" ? labels.haloSize : 0;

otef-interactive/frontend/js/shared/map-projection-config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ const MapProjectionConfig = {
1010
ENABLE_MAP_VISIBILITY_DEBUG: false,
1111
ENABLE_PROJECTION_DEBUG: false,
1212

13+
// Global scale factor for label font sizes on the projector (canvas renderer only).
14+
// 1.0 -> use sizes exported from styles.json as-is
15+
// <1.0 -> shrink labels relative to exported sizes (e.g. 0.35 for ~1/3 size)
16+
// >1.0 -> enlarge labels (generally not recommended)
17+
LABEL_SIZE_SCALE: 0.25,
18+
1319
// Projection highlight smoothing (LERP factor)
1420
// Lower = smoother/slower, Higher = snappier
1521
PROJECTION_LERP_FACTOR: 0.15,

otef-interactive/scripts/otef_layer_processing/styles.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,74 @@ def _build_advanced_symbol_from_layers(symbol_layers: List[Dict]) -> Dict[str, A
476476
return {"symbolLayers": symbol_layers_ir}
477477

478478

479+
def _ensure_advanced_stroke_for_polygons(style: StyleConfig) -> None:
480+
"""
481+
For polygon layers, ensure advanced_symbol has a visible stroke that matches
482+
the simplified default_style when the CIM strokes are effectively invisible.
483+
484+
This keeps projector outlines consistent with GIS, while still allowing
485+
fully transparent strokes when both CIM and default_style agree on "no stroke".
486+
"""
487+
if not style or style.geometry_type != "polygon":
488+
return
489+
490+
advanced = style.advanced_symbol
491+
default_style = style.default_style or {}
492+
493+
if not isinstance(advanced, dict):
494+
return
495+
496+
symbol_layers = advanced.get("symbolLayers")
497+
if not isinstance(symbol_layers, list) or not symbol_layers:
498+
return
499+
500+
stroke_layers = [
501+
(idx, layer)
502+
for idx, layer in enumerate(symbol_layers)
503+
if isinstance(layer, dict) and layer.get("type") == "stroke"
504+
]
505+
if not stroke_layers and not default_style:
506+
return
507+
508+
# Check if we already have a visible stroke (opacity > 0 and width > 0)
509+
has_visible_stroke = any(
510+
(layer.get("opacity", 1.0) > 0.0) and (layer.get("width", 1.0) > 0.0)
511+
for _, layer in stroke_layers
512+
)
513+
if has_visible_stroke:
514+
return
515+
516+
stroke_color = default_style.get("strokeColor")
517+
stroke_width = default_style.get("strokeWidth")
518+
if not stroke_color or stroke_width is None or stroke_width <= 0:
519+
# Default style does not declare a meaningful stroke; respect that.
520+
return
521+
522+
dash_array = default_style.get("dashArray")
523+
new_stroke = {
524+
"type": "stroke",
525+
"color": stroke_color,
526+
"width": stroke_width,
527+
"opacity": 1.0,
528+
"dash": {"array": dash_array} if dash_array else None,
529+
}
530+
531+
if stroke_layers:
532+
# We only had invisible strokes; replace the first one with a visible
533+
# outline that matches the simplified default style.
534+
first_idx, _ = stroke_layers[0]
535+
symbol_layers[first_idx] = new_stroke
536+
else:
537+
# No stroke layers at all: insert a stroke before the first fill so
538+
# drawing order remains outline-under-fill.
539+
insert_idx = 0
540+
for i, layer in enumerate(symbol_layers):
541+
if isinstance(layer, dict) and layer.get("type") == "fill":
542+
insert_idx = i
543+
break
544+
symbol_layers.insert(insert_idx, new_stroke)
545+
546+
479547
def parse_lyrx_style(lyrx_path: Path) -> Optional[StyleConfig]:
480548
try:
481549
with open(lyrx_path, "r", encoding="utf-8") as f:
@@ -646,6 +714,10 @@ def parse_lyrx_style(lyrx_path: Path) -> Optional[StyleConfig]:
646714
style.default_style = extract_simplified_style(all_layers)
647715
style.advanced_symbol = _build_advanced_symbol_from_layers(all_layers) or None
648716

717+
# For polygon layers, align advanced_symbol strokes with the simplified
718+
# default_style when CIM-only information would otherwise drop the outline.
719+
_ensure_advanced_stroke_for_polygons(style)
720+
649721
if not style.default_style:
650722
# Absolute fallback
651723
style.default_style = {

0 commit comments

Comments
 (0)