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/public/toolConfigs.json

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion configure/src/metaconfigs/layer-image-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,15 @@
"name": "Legend From URL",
"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
"width": 10
},
{
"new": true,
"field": "variables.hideLegendLayerName",
"name": "Hide layer name",
"description": "Hide layer name in the legend.",
"type": "checkbox",
"width": 2
}
]
},
Expand Down
10 changes: 9 additions & 1 deletion configure/src/metaconfigs/layer-model-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,15 @@
"name": "Legend From URL",
"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
"width": 10
},
{
"new": true,
"field": "variables.hideLegendLayerName",
"name": "Hide layer name",
"description": "Hide layer name in the legend.",
"type": "checkbox",
"width": 2
}
]
},
Expand Down
10 changes: 9 additions & 1 deletion configure/src/metaconfigs/layer-query-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,15 @@
"name": "Legend From URL",
"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
"width": 10
},
{
"new": true,
"field": "variables.hideLegendLayerName",
"name": "Hide layer name",
"description": "Hide layer name in the legend.",
"type": "checkbox",
"width": 2
}
]
},
Expand Down
10 changes: 9 additions & 1 deletion configure/src/metaconfigs/layer-tile-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,15 @@
"name": "Legend From URL",
"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
"width": 10
},
{
"new": true,
"field": "variables.hideLegendLayerName",
"name": "Hide layer name",
"description": "Hide layer name in the legend.",
"type": "checkbox",
"width": 2
}
]
},
Expand Down
10 changes: 9 additions & 1 deletion configure/src/metaconfigs/layer-vector-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,15 @@
"name": "Legend From URL",
"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
"width": 10
},
{
"new": true,
"field": "variables.hideLegendLayerName",
"name": "Hide layer name",
"description": "Hide layer name in the legend.",
"type": "checkbox",
"width": 2
}
]
},
Expand Down
10 changes: 9 additions & 1 deletion configure/src/metaconfigs/layer-vectortile-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,15 @@
"name": "Legend From URL",
"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
"width": 10
},
{
"new": true,
"field": "variables.hideLegendLayerName",
"name": "Hide layer name",
"description": "Hide layer name in the legend.",
"type": "checkbox",
"width": 2
}
]
},
Expand Down
10 changes: 9 additions & 1 deletion configure/src/metaconfigs/layer-velocity-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,15 @@
"name": "Legend From URL",
"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
"width": 10
},
{
"new": true,
"field": "variables.hideLegendLayerName",
"name": "Hide layer name",
"description": "Hide layer name in the legend.",
"type": "checkbox",
"width": 2
}
]
},
Expand Down
166 changes: 105 additions & 61 deletions src/essence/Tools/Legend/LegendTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var LegendTool = {
//Get tool variables
this.displayOnStart = L_.getToolVars('legend')['displayOnStart']
this.justification = L_.getToolVars('legend')['justification']
this.showHeadersInLegend = L_.getToolVars('legend')['showHeadersInLegend']
if (this.justification == 'right') {
const toolController = d3.select('#toolcontroller_sepdiv')
const toolContent = d3.select('#toolContentSeparated_Legend')
Expand Down Expand Up @@ -99,78 +100,109 @@ function interfaceWithMMWebGIS() {
function refreshLegends() {
$('#LegendTool').empty()

for (let l in L_.layers.on) {
if (L_.layers.on[l] == true) {
if (L_.layers.data[l].type != 'header') {
if (L_.layers.data[l]?._legend === undefined
&& ((['image', 'tile'].includes(L_.layers.data[l].type) && L_.layers.data[l].cogTransform)
|| L_.layers.data[l].type === 'velocity')) {
const layersTool = ToolController_.getTool('LayersTool')
layersTool.populateCogScale(L_.layers.data[l].name)
}
function _refreshLegends(node, parent, depth) {
let shift = LegendTool.showHeadersInLegend === true ? depth : 0
for (let i in node) {
let l = node[i].name
if (L_.layers.on[l] == true) {
if (L_.layers.data[l].type != 'header') {
if (L_.layers.data[l]?._legend === undefined
&& ((['image', 'tile'].includes(L_.layers.data[l].type) && L_.layers.data[l].cogTransform)
|| L_.layers.data[l].type === 'velocity')) {
const layersTool = ToolController_.getTool('LayersTool')
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
// 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)
}
} 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],
shift
)
continue; // Skip the CSV processing below
}
}

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

if (L_.layers.data[l]?._legend != undefined) {
drawLegends(
LegendTool.tools,
L_.layers.data[l]?._legend,
l,
L_.layers.data[l].display_name,
L_.layers.opacity[l]
)
}
} else if (LegendTool.showHeadersInLegend === true) {
const haveLegends = L_.layers.data[l].sublayers
.map(i => i.name)
.filter(i => {
return ((L_.layers.data[i]._legend?.length > 0
|| (L_.layers.data[i]?._legend === undefined
&& ((['image', 'tile'].includes(L_.layers.data[i].type) && L_.layers.data[i].cogTransform)
|| L_.layers.data[i].type === 'velocity'))) && L_.layers.on[i])
})

if (haveLegends.length > 0) {
drawLegends(
LegendTool.tools,
L_.layers.data[l]?._legend,
l,
L_.layers.data[l].display_name,
L_.layers.opacity[l],
shift
)
}
}
}

if (node[i].sublayers)
_refreshLegends(node[i].sublayers, node[i], depth + 1)
}
}

_refreshLegends(L_.configData.layers, {}, 0)
}

// The legends parameter should be an array of objects, where each object must contain
Expand Down Expand Up @@ -244,25 +276,37 @@ function drawLegendHeader() {
return tools
}

function drawLegends(tools, _legend, layerUUID, display_name, opacity) {
function drawLegends(tools, _legend, layerUUID, display_name, opacity, shift) {
if (tools == null) return

const layerConfig = L_.layers.data[layerUUID]

const isHeader = layerConfig.type === 'header'

// If option to hide layer name in legend is checked in the configuration
const hideLegendLayerName = layerConfig.variables?.hideLegendLayerName || false;

var c = tools
.append('div')
.attr('class', 'mmgisScrollbar')
.style('width', '100%')
.style('display', 'inline-block')
.style('padding-top', '5px')
.style('padding-right', '12px')
.style('border-bottom', '1px solid var(--color-i)')
.style('padding-left', shift > 0 ? `${shift * 16}px` : '')
.style('border-bottom', isHeader ? '' : '1px solid var(--color-i)')

c.append('div')
.attr('class', 'row')
.append('p')
.style('font-size', '13px')
.style('color', 'var(--color-f)')
.style('margin-bottom', '5px')
.style('margin-bottom', isHeader ? '' : '5px')
.style('padding-left', '8px')
.text(display_name)
.style('font-weight', isHeader ? 'bold' : '')
.text(hideLegendLayerName ? '' : display_name)

if (isHeader) return

let lastContinues = []
let lastShape = ''
Expand Down
8 changes: 8 additions & 0 deletions src/essence/Tools/Legend/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@
"type": "dropdown",
"width": 2,
"options": ["left", "right"]
},
{
"field": "variables.showHeadersInLegend",
"name": "Show Headers in Legend",
"description": "If true, the legend will display the name of Header layer types.",
"type": "checkbox",
"width": 3,
"defaultChecked": false
}
]
}
Expand Down