Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 configure/src/metaconfigs/layer-image-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
"new": true,
"field": "legend",
"name": "Legend From URL",
"description": "A URL to a .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"type": "text",
"width": 12
}
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-model-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@
"new": true,
"field": "legend",
"name": "Legend From URL",
"description": "A URL to a .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"type": "text",
"width": 12
}
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-query-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@
"new": true,
"field": "legend",
"name": "Legend From URL",
"description": "A URL to a .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"type": "text",
"width": 12
}
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-tile-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@
"new": true,
"field": "legend",
"name": "Legend From URL",
"description": "A URL to a .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"type": "text",
"width": 12
}
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-vector-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@
"new": true,
"field": "legend",
"name": "Legend From URL",
"description": "A URL to a .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"type": "text",
"width": 12
}
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-vectortile-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@
"new": true,
"field": "legend",
"name": "Legend From URL",
"description": "A URL to a .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"type": "text",
"width": 12
}
Expand Down
2 changes: 1 addition & 1 deletion configure/src/metaconfigs/layer-velocity-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@
"new": true,
"field": "legend",
"name": "Legend From URL",
"description": "A URL to a .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"description": "A URL to a static image or .csv with the following header: 'color,strokecolor,shape,value'. If the path is relative, it will be relative to the mission's directory. This legend is overridden if a legend is also configured below.",
"type": "text",
"width": 12
}
Expand Down
5 changes: 5 additions & 0 deletions src/essence/Basics/Formulae_/Formulae_.js
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,11 @@ var Formulae_ = {
},
csvToJSON: function (csv) {
if (csv == null) return {}

// Ensure csv is a string
if (typeof csv !== 'string') {
return {}
}

var lines = csv.split('\n')
var result = []
Expand Down
97 changes: 96 additions & 1 deletion src/essence/Tools/Legend/LegendTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,56 @@ function refreshLegends() {
layersTool.populateCogScale(L_.layers.data[l].name)
}

// Check if there's a legend URL that points to an image
const legendURL = L_.layers.data[l]?.legend
if (legendURL && typeof legendURL === 'string') {
let isImageUrl = false

// First check for file extensions
const fileExtension = legendURL.toLowerCase().split('.').pop().split('?')[0] // Remove query params
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'tiff', 'tif', 'bmp', 'ico', 'avif']

if (imageExtensions.includes(fileExtension)) {
isImageUrl = true
} else if (['csv'].includes(fileExtension)) {
isImageUrl = false
} else {
// If no file extension and not a csv, check for image MIME types in URL parameters (e.g., WMS GetLegendGraphic)
try {
const url = new URL(legendURL)
const formatParam = url.searchParams.get('FORMAT') || url.searchParams.get('format')

if (formatParam) {
const imageMimeTypes = [
'image/png', 'image/jpeg', 'image/jpg', 'image/gif',
'image/svg+xml', 'image/webp', 'image/tiff',
'image/bmp', 'image/ico', 'image/avif'
]

const decodedFormat = decodeURIComponent(formatParam).toLowerCase()
if (imageMimeTypes.includes(decodedFormat)) {
isImageUrl = true
}
}
} catch (e) {
// URL parsing failed, treat as non-image
console.warn('Failed to parse legend URL:', legendURL)
}
}

if (isImageUrl) {
// Handle image legend directly
drawLegends(
LegendTool.tools,
legendURL, // Pass the URL string directly
l,
L_.layers.data[l].display_name,
L_.layers.opacity[l]
)
continue; // Skip the CSV processing below
}
}

if (L_.layers.data[l]?._legend != undefined) {
drawLegends(
LegendTool.tools,
Expand Down Expand Up @@ -216,6 +266,51 @@ function drawLegends(tools, _legend, layerUUID, display_name, opacity) {

let lastContinues = []
let lastShape = ''

// Check if _legend is an image URL (string)
if (typeof _legend === 'string') {
// Render image directly
const imageContainer = c
.append('div')
.attr('class', 'legend-image-container')
.style('display', 'flex')
.style('justify-content', 'center')
.style('margin', '4px')
.style('padding', '4px')
.style('overflow-x', 'hidden')
imageContainer
.append('img')
.attr('src', _legend.startsWith('http') ? _legend : L_.missionPath + _legend)
.attr('alt', `Legend for ${display_name}`)
.style('max-width', '300px')
.style('max-height', '220px')
.style('height', 'auto')
.style('background-color', 'white')
.style('border', '1px solid var(--color-i)')
.style('border-radius', '3px')
.style('opacity', opacity)
.on('load', function() {
// Set container max-width to image width (capped at 300px)
const maxImageWidth = Math.min(this.naturalWidth, 300)
imageContainer
.style('max-width', maxImageWidth + 'px')
.style('width', 'fit-content')
})
.on('error', function() {
// Handle image load error
d3.select(this.parentNode)
.append('div')
.style('color', '#ff6b6b')
.style('padding', '8px')
.style('text-align', 'center')
.style('font-size', '12px')
.text('Failed to load legend.')
d3.select(this).remove()
})

return // Exit early since we've rendered the image
}

for (let d in _legend) {
var shape = _legend[d].shapeImage && _legend[d].shapeImage.trim()
? _legend[d].shapeImage : _legend[d].shapeIcon && _legend[d].shapeIcon.trim()
Expand All @@ -234,7 +329,7 @@ function drawLegends(tools, _legend, layerUUID, display_name, opacity) {
})
lastShape = shape
} else {

// finalize discreet and continuous
if (lastContinues.length > 0) {
pushScale(lastContinues)
Expand Down