diff --git a/code/hcat/HCAT3.csv b/code/hcat/HCAT3.csv new file mode 100644 index 0000000..43e4c26 --- /dev/null +++ b/code/hcat/HCAT3.csv @@ -0,0 +1,385 @@ +HCAT3_name,HCAT3_code +characteristics,3000000000 +crop_type,3300000000 +arable_crops,3301000000 +cereal,3301010000 +common_soft_wheat,3301010100 +winter_common_soft_wheat,3301010101 +spring_common_soft_wheat,3301010102 +unspecified_season_common_soft_wheat,3301010199 +durum_hard_wheat,3301010200 +winter_durum_hard_wheat,3301010201 +spring_durum_hard_wheat,3301010202 +unspecified_season_durum_hard_wheat,3301010299 +rye,3301010300 +winter_rye,3301010301 +spring_rye,3301010302 +unspecified_season_rye,3301010399 +barley,3301010400 +winter_barley,3301010401 +spring_barley,3301010402 +unspecified_season_barley,3301010499 +oats,3301010500 +winter_oats,3301010501 +spring_oats,3301010502 +unspecified_season_oats,3301010599 +grain_maize_corn_popcorn,3301010600 +unspecified_season_grain_maize_corn_popcorn,3301010699 +rice,3301010700 +unspecified_season_rice,3301010799 +triticale,3301010800 +winter_triticale,3301010801 +spring_triticale,3301010802 +unspecified_season_triticale,3301010899 +millet_sorghum,3301010900 +winter_millet_sorghum,3301010901 +spring_millet_sorghum,3301010902 +teff,3301010904 +unspecified_season_millet_sorghum,3301010999 +spelt,3301011000 +winter_spelt,3301011001 +spring_spelt,3301011002 +unspecified_season_spelt,3301011099 +meslin,3301011100 +winter_meslin,3301011101 +spring_meslin,3301011102 +unspecified_season_meslin,3301011199 +emmer,3301011200 +winter_emmer,3301011201 +spring_emmer,3301011202 +unspecified_season_emmer,3301011299 +einkorn,3301011300 +winter_einkorn,3301011301 +spring_einkorn,3301011302 +unspecified_season_einkorn,3301011399 +canary_seed_canaryseed,3301011400 +unspecified_season_canary_seed_canaryseed,3301011499 +unspecified_cereals,3301011500 +winter_unspecified_cereals,3301011501 +spring_unspecified_cereals,3301011502 +summer_unspecified_cereals,3301011503 +unspecified_season_unspecified_cereals,3301011599 +other_cereals,3301019900 +unspecified_season_other_cereals,3301019999 +legumes_dried_pulses_protein_crops,3301020000 +beans,3301020100 +chickpeas,3301020200 +esparsette_onobrychis,3301020300 +fenugreek,3301020400 +lentils,3301020500 +peas,3301020600 +sweet_lupins,3301020700 +unspecified_legumes_dried_pulses_protein_crops,3301029800 +other_dry_pulses,3301029900 +potatoes,3301030000 +sweet_potatoes,3301040000 +fodder_roots,3301050000 +industrial_nonfood_crops,3301060000 +tobacco,3301060100 +hops,3301060200 +cotton,3301060300 +rapeseed_rape,3301060400 +winter_rapeseed_rape,3301060401 +spring_rapeseed_rape,3301060402 +summer_rapeseed_rape,3301060403 +unspecified_season_rapeseed_rape,3301060499 +sunflower,3301060500 +poppy,3301060600 +winter_poppy,3301060601 +summer_poppy,3301060602 +flax_linseed,3301060700 +flax_linen,3301060701 +flax_linseed_oil,3301060702 +oilseed_crops,3301060800 +guizotia_abyssinica_nyger,3301060900 +hemp_cannabis,3301061000 +finola,3301061001 +fibre_crops,3301061100 +aromatic_medicinal_culinary_plants_spices_herbs,3301061200 +actaea_baneberry_christopher_herbs,3301061201 +alchemilla_ladys_mantle,3301061202 +anethum_dill,3301061203 +angelica,3301061204 +anise_aniseed,3301061205 +artemisia,3301061206 +basil,3301061207 +black_cumin,3301061208 +borage,3301061209 +calendula_marigold,3301061210 +caraway,3301061211 +catnip,3301061212 +chamomile,3301061213 +chervil,3301061214 +coriander,3301061215 +ericaceae_heather,3301061216 +galium_bedstraw,3301061217 +hibiscus,3301061218 +lavender_lavandula,3301061219 +lemon_balm_melissa,3301061220 +lovage_maggiplant,3301061221 +mints_peppermint,3301061222 +moldavian_dragonhead,3301061223 +nasturtiums,3301061224 +nettles,3301061225 +oregano,3301061226 +parsly,3301061227 +piper_pepper,3301061228 +polygonum,3301061229 +rosemary,3301061230 +rubia_tinctorum_common_madder,3301061231 +saffron_crocus_sativus,3301061232 +silver_comb,3301061233 +st_johns_wort,3301061234 +stachys_hedgenettle_chinese_artichoke,3301061235 +tarragon,3301061236 +thyme,3301061237 +valerian,3301061238 +yarrow,3301061239 +unspecified_aromatic_medicinal_culinary_plants_spices_herbs,3301061298 +other_aromatic_medicinal_culinary_plants_spices_herbs,3301061299 +marian_thistles,3301061300 +phacelia,3301061400 +camelina,3301061500 +onobrychis_sainfoins,3301061600 +other_industrial_crops,3301069900 +fresh_vegetables,3301070000 +flowers_ornamental_plants,3301080000 +adonis,3301080100 +anemones_windflowers,3301080200 +asters,3301080300 +begonias,3301080400 +bluebells,3301080500 +bulrush,3301080600 +burnet,3301080700 +carnation,3301080800 +chrysanthemum,3301080900 +cornflowers,3301081000 +corsican_hellebore,3301081100 +dahlia,3301081200 +daisy_daisies,3301081300 +dandelions,3301081400 +echinacea_sun_hat,3301081500 +edelweiss,3301081600 +fiddleneck_amsinckia,3301081700 +fuchsias,3301081800 +galega,3301081900 +gentians,3301082000 +gladiolus_gladioli,3301082100 +goldenrod,3301082200 +iris,3301082300 +isatis_tinctoria_woad,3301082400 +lilies,3301082500 +lotus,3301082600 +lunaria_honesty_silver,3301082700 +malva,3301082800 +milk_star,3301082900 +miscanthus_silvergrass,3301083000 +monstera_adansonii_eyelet,3301083100 +moonseed,3301083200 +narcissus_daffodil,3301083300 +peony_peonies,3301083400 +primrose,3301083500 +rhododendron,3301083600 +roses,3301083700 +rudbeckia_coneflowers,3301083800 +safflower,3301083900 +salsify,3301084000 +sanvitalia_procumbens,3301084100 +serradella,3301084200 +silene_catchfly,3301084300 +silphium_rosinweeds,3301084400 +snapdragons,3301084500 +stonecrop,3301084600 +tagetes,3301084700 +thimbles,3301084800 +tulips,3301084900 +viola,3301085000 +violets_pansies,3301085100 +zinnias,3301085200 +unspecified_flowers_ornamental_plants,3301089800 +other_flowers_ornamental_plants,3301089900 +plants_harvested_green,3301090000 +temporary_grass,3301090100 +poaceae_grasses,3301090200 +elymus,3301090201 +festuca_fescue,3301090202 +cocksfoot_catgrass,3301090203 +festulolium,3301090204 +lolium_ryegrass,3301090205 +setaria,3301090206 +sod_turf,3301090207 +switchgrass,3301090208 +timothy,3301090209 +legumes_harvested_green,3301090300 +alfalfa_lucerne,3301090301 +arachis,3301090302 +clover,3301090303 +melilot,3301090304 +vetches,3301090305 +unspecified_legumes_harvested_green,3301090398 +green_silo_maize,3301090400 +other_plants_harvested_green,3301099900 +arable_land_seed_seedlings,3301100000 +fallow_land_not_crop,3301110000 +kitchen_gardens,3301120000 +strawberries,3301130000 +cucurbits,3301140000 +cucumber_pickle,3301140100 +honeydew,3301140200 +melon,3301140300 +pumpkin_squash_gourd,3301140400 +watermelon,3301140500 +zucchini_courgette,3301140600 +pseudocereal,3301150000 +amaranth,3301150100 +buckwheat,3301150200 +quinoa,3301150300 +soy_soybeans,3301160000 +fennel,3301170000 +topinambur_jerusalem_artichoke,3301180000 +sage_chia,3301190000 +asparagus,3301200000 +brassicaceae_cruciferae,3301210000 +mustard,3301210100 +brassica_oleracea_cabbage,3301210200 +bok_choy_pak_choi,3301210201 +broccoli,3301210202 +brussels_sprouts,3301210203 +cauliflower,3301210204 +chinese_cabbage,3301210205 +collard_greens,3301210206 +gai_lan,3301210207 +kale,3301210208 +kohlrabi,3301210209 +red_cabbage,3301210210 +savoy_cabbage,3301210211 +white_cabbage,3301210212 +other_brassica_oleracea_cabbage,3301210299 +cress,3301210300 +horseradish,3301210400 +swede_rutabaga,3301210500 +alliums,3301220000 +chives,3301220100 +garlic,3301220200 +leek,3301220300 +onions,3301220400 +scallion,3301220500 +shallot,3301220600 +rhubarb,3301230000 +purslane,3301240000 +celery,3301250000 +celeriac,3301250100 +leaf_celery,3301250200 +aubergine_eggplant,3301260000 +artichoke,3301270000 +tomato,3301280000 +root_vegetables,3301290000 +arctium_burdock,3301290100 +beetroot_beets,3301290200 +carrots_daucus,3301290300 +mangelwurzel_fodder_beet,3301290400 +parsnips,3301290500 +radish,3301290600 +sugar_beet,3301290700 +turnips,3301290800 +unspecified_root_vegetables,3301299800 +capsicum,3301300000 +bell_pepper_paprika,3301300100 +chili_pepper,3301300200 +salads_lettuce_leaf_vegetables,3301310000 +chard,3301310100 +chicory_chicories,3301310200 +endive,3301310300 +iceberg,3301310400 +lambs_lettuce_rapunzel,3301310500 +rocket_arugula,3301310600 +sorrel,3301310700 +spinach,3301310800 +other_salads_lettuce_leaf_vegetables,3301319900 +other_arable_land_crops,3301990000 +pasture_meadow_grassland_grass,3302000000 +permanent_crops_perennial,3303000000 +orchards_fruits,3303010000 +amelanchier_serviceberry,3303010100 +apples,3303010200 +apricots,3303010300 +cherry_cherries,3303010400 +feijoa,3303010500 +fig,3303010600 +kiwi,3303010700 +medlar_loquat,3303010800 +nectarine,3303010900 +pawpaw,3303011000 +peach,3303011100 +pears,3303011200 +plums,3303011300 +pomegranate,3303011400 +quinces,3303011500 +unspecified_orchards_fruits,3303019800 +berries_berry_species,3303020000 +aronia_chokeberries,3303020100 +blackberry,3303020200 +blackcurrant_cassis,3303020300 +blueberry,3303020400 +cranberry,3303020500 +currants,3303020600 +gooseberry_gooseberries_cranberries,3303020700 +hippophae_sea_buckthorns_seaberry,3303020800 +jostaberry,3303020900 +raspberry_raspberries,3303021000 +redcurrant,3303021100 +rose_hip_rosehip,3303021200 +rowan_rowanberries,3303021300 +tayberry,3303021400 +unspecified_berries_berry_species,3303029800 +nuts,3303030000 +almond,3303030100 +hazelnuts_hazel,3303030200 +pecan,3303030300 +pistachio,3303030400 +sweet_chestnuts,3303030500 +walnuts,3303030600 +citrus_plantations,3303040000 +olive_plantations,3303050000 +olives_for_oil_production,3303050100 +table_olives,3303050200 +vineyards_wine_vine_rebland_grapes,3303060000 +nurseries_nursery,3303070000 +shrubberries_shrubs,3303080000 +azaleas,3303080100 +chaenomeles_cathayensis,3303080200 +crataegus_hawthorn,3303080300 +elder_elderberry,3303080400 +honeysuckle,3303080500 +ricinus_castor,3303080600 +wire_bush,3303080700 +ginko,3303090000 +avocado,3303100000 +legumes_from_trees,3303110000 +carob,3303110100 +mesquite,3303110200 +tamarind,3303110300 +unspecified_permanent_crops,3303120000 +other_permanent_crops_plantations,3303990000 +mushrooms_energy_genetically_modified_crops,3304000000 +energy_crops,3304010000 +genetically_modified_crops,3304020000 +igniscum_candy,3304020100 +sida_virginia_mallow,3304030000 +truffle,3304040000 +other_mushrooms_energy_crops_genetically_modified_crops,3304990000 +greenhouse_foil_film,3305000000 +tree_wood_forest,3306000000 +afforestation_reforestation,3306010000 +aspen,3306020000 +birch,3306030000 +dogwood_cornus,3306040000 +eucalyptus,3306050000 +oak,3306060000 +populus,3306070000 +willows_osiers,3306080000 +unspecified_tree_wood_forest,3306980000 +other_tree_wood_forest,3306990000 +peat_turf,3307000000 +unmaintained,3308000000 +not_known_and_other,3399000000 \ No newline at end of file diff --git a/map/.gitignore b/map/.gitignore index 7f5e283..787ad82 100644 --- a/map/.gitignore +++ b/map/.gitignore @@ -1,3 +1,4 @@ node_modules dist -/sources.js \ No newline at end of file +sources.js +crop/codes.js diff --git a/map/crop/CropLegendControl.js b/map/crop/CropLegendControl.js new file mode 100644 index 0000000..85a4924 --- /dev/null +++ b/map/crop/CropLegendControl.js @@ -0,0 +1,149 @@ +import Control from 'ol/control/Control'; +import VectorTile from 'ol/layer/VectorTile'; +import {toGeometry} from "ol/render/Feature"; +import TileState from "ol/TileState"; +import {intersects} from "ol/extent"; +import debounce from "lodash.debounce"; + +function forFeaturesInExtent(vt, extent, callback) { + const renderer = vt.getRenderer(); + const tileCache = renderer.getTileCache(); + if (tileCache.getCount() === 0) return; + + const tileGrid = renderer.getLayer().getSource().tileGrid; + const z = tileGrid.getZForResolution(renderer.renderedResolution); + tileCache.forEach((tile) => { + if (tile.tileCoord[0] !== z || tile.getState() !== TileState.LOADED) return; + for (const sourceTile of tile.getSourceTiles()) { + const tileCoord = sourceTile.tileCoord; + if (intersects(extent, tileGrid.getTileCoordExtent(tileCoord))) { + for (const candidate of sourceTile.getFeatures()) { + if (intersects(extent, candidate.getExtent())) callback(candidate); + } + } + } + }); +} + +/** + * A custom Legend Control for OpenLayers + * Displays a legend on the map with custom items + */ +export class CropLegendControl extends Control { + constructor(options = {}) { + const element = document.createElement('div'); + element.className = 'ol-crop-legend ol-unselectable ol-control'; + + super({ + element: element, + target: options.target, + }); + + this.attribute = options.attribute; + this.mapping = options.mapping || []; + this.legendItems = []; + this.level = this.mapping.length - 1; + this.render(); + this.tileLoadEnd = this.tileLoadEnd.bind(this); + this.changeSource = this.changeSource.bind(this); + this.updateFeatureCount = debounce(this.updateFeatureCount, 1000).bind(this); + } + updateFeatureCount(e) { + const map = this.getMap(); + const mapping = this.mapping[this.level]; + const vectorTiles = map.getLayers().getArray().filter(f => f instanceof VectorTile); + const extent = map.getView().calculateExtentInternal(); + let totalArea = 0, count = 0, cropArea = {}, cropCount = {}, byName = {}; + for (const vt of vectorTiles) { + forFeaturesInExtent(vt, extent, (feature) => { + const area = feature.properties_["area"] || toGeometry(feature).getArea(); + const crop = feature.properties_[this.attribute]; + const name = mapping[crop]?.name; + if (!byName[name]) byName[name] = mapping[crop]; + count++; + totalArea += area; + cropCount[name] = (cropCount[name] || 0) + 1; + cropArea[name] = (cropArea[name] || 0) + area; + }); + } + const topCrops = Object.entries(cropArea) + .map(([name, area]) => ({ name, area })) + .sort((a, b) => b.area - a.area) // Descending order + .slice(0, 5); + + this.legendItems = topCrops.map(({name, area}) => + { + const c = byName[name]; + const percent = (area / totalArea) * 100; + return { + label: c.name.replaceAll("_", " "), + color: c.color || "#99bbccaa", + percent: percent >= 1 ? percent.toFixed(0) + "%" : "<1%", + area: area.toFixed(2), + } + } + ) + this.render(); + } + + tileLoadEnd(e) { + const features = e.tile.getFeatures(); + const m = this.mapping.at(-1); + for (const feature of features) { + const p = feature.getProperties(); + p.color = m[p[this.attribute]]?.color || "#99bbccaa"; + } + this.updateFeatureCount(e) + } + + changeSource(e) { + if (!(e.element instanceof VectorTile)) return; + if (e.type === "add") { + e.element.getSource().on('tileloadend', this.tileLoadEnd); + } else { + e.element.getSource().un('tileloadend', this.tileLoadEnd); + } + } + + setMap(map) { + super.setMap(map); + const layers = map.getLayers(); + if (map) { + map.on("moveend", this.updateFeatureCount); + layers.on(["add", "remove"], this.changeSource) + } else { + map.un("moveend", this.updateFeatureCount); + layers.un(["add", "remove"], this.changeSource) + } + } + render() { + const element = this.element; + if (!this.legendItems?.length) { + element.innerHTML = ""; + return; + } + let levels = ""; + if (this.mapping.length) { + levels = `Levels: `; + for (let i = 0; i < this.mapping.length; i++) { + levels += ``; + } + } + element.innerHTML = ` +
Legend ${levels}
+ ${this.legendItems.map(({color, label, percent}) => ` + + `).join("")} + `; + this.element.querySelectorAll(".legend-level").forEach(e => e.addEventListener("click", (e) => { + this.level = parseInt(e.target.innerText); + this.updateFeatureCount(); + })) + } + +} diff --git a/map/crop/crop.js b/map/crop/crop.js new file mode 100644 index 0000000..8e30c83 --- /dev/null +++ b/map/crop/crop.js @@ -0,0 +1,42 @@ +import collections from '../sources' +import {FiboaMap} from "../map"; +import {hcat} from "./codes"; +import VectorTile from "ol/layer/VectorTile"; +import {PMTilesVectorSource} from "ol-pmtiles"; +const cropExtension = "https://fiboa.github.io/hcat-extension/v0.1.0/schema.yaml"; +import {CropLegendControl} from "./CropLegendControl"; + +const CROP_ATTRIBUTE = "ec:hcat_code"; +const fieldStyle = { + "stroke-color": 'rgb(88, 88, 88, 88)', + "stroke-width": 0.5, + "fill-color": ['get', 'color'] +} +const hcats = hcat.map(c => [c.code, c]) +const mp = Object.fromEntries(hcats); +const mapping = [4,6,8,10].map(l => Object.fromEntries(hcat.map(c => [c.code, mp[c.code.slice(0,l).padEnd(c.code.length, "0")]]))); + +class CropMap extends FiboaMap { + constructor() { + super(); + this.fieldStyle = fieldStyle; + this.map.addControl(new CropLegendControl({attribute: CROP_ATTRIBUTE, mapping})); + } + + addPMTilesLayer(c, options) { + const source = new PMTilesVectorSource(options); + const fields = new VectorTile({ + declutter: true, + title: c.title, + displayInLayerSwitcher: true, + minZoom: this.minZoom, + source: source, + style: this.fieldStyle, + }); + this.map.addLayer(fields); + } +} + +const cropCollections = collections.filter(c => c.fiboa_extensions?.includes(cropExtension)); +const m = new CropMap(); +m.addCollections(cropCollections); diff --git a/map/crop/index.html b/map/crop/index.html new file mode 100644 index 0000000..efb6385 --- /dev/null +++ b/map/crop/index.html @@ -0,0 +1,19 @@ + + + + + + + Map of Fiboa HCAT datasets + + +

+ Fiboa HCAT datasets + 0 field boundaries +

+
+ +
Zoom in to show the field boundaries
+ + + diff --git a/map/main.js b/map/main.js index ab90344..1c156c1 100644 --- a/map/main.js +++ b/map/main.js @@ -1,184 +1,5 @@ -import "./style.css"; -import { Feature, Map, Overlay, View } from "ol"; -import { fromExtent } from "ol/geom/Polygon"; -import TileLayer from 'ol/layer/WebGLTile.js'; -import VectorTile from "ol/layer/VectorTile"; -import VectorLayer from "ol/layer/Vector"; -import { useGeographic } from 'ol/proj'; -import { OSM, Vector } from "ol/source"; -import Style from "ol/style/Style"; -import Stroke from "ol/style/Stroke"; -import LayerSwitcher from "ol-ext/control/LayerSwitcher" -import { PMTilesVectorSource } from "ol-pmtiles"; import collections from './sources' -import Fill from "ol/style/Fill"; +import {FiboaMap} from "./map"; -useGeographic(); - -const minZoom = 7; - -const map = new Map({ - target: 'map', - layers: [ - new TileLayer({ - displayInLayerSwitcher: false, - source: new OSM() - }) - ], - view: new View({ - projection: 'EPSG:3857', - center: [0, 20], - zoom: 1 - }), - controls: [ - new LayerSwitcher({ - show_progress: true, - extent: true - }) - ] -}); - -const bboxStroke = new Stroke({ - color: 'rgb(0, 0, 0)', - width: 1 -}); -const bboxRedStroke = new Stroke({ - color: 'rgb(255, 0, 0)', - width: 1 -}); -const bboxFill = new Fill({ - color: 'rgba(0, 0, 0, 0.1)' -}); -const fieldStyle = new Style({ - stroke: new Stroke({ - color: 'rgb(0, 165, 255)', - width: 1 - }), - fill: new Fill({ - color: 'rgba(0, 165, 255, 0.1)' - }) -}); - -let count = 0; -for (const c of collections) { - const bboxes = c.bbox.length > 1 ? c.bbox.slice(1) : c.bbox; - const features = []; - for (const bbox of bboxes) { - const feature = new Feature(fromExtent(bbox)); - feature.setProperties(c); - features.push(feature); - } - const bboxLayer = new VectorLayer({ - title: c.title, - displayInLayerSwitcher: false, - maxZoom: c.pmtiles ? minZoom : undefined, - source: new Vector({ features }), - style: new Style({ - stroke: c.pmtiles ? bboxStroke : bboxRedStroke, - fill: bboxFill - }) - }); - map.addLayer(bboxLayer); - - if (c.count > 0) { - count += c.count; - } - - if (c.pmtiles) { - const options = { - url: c.pmtiles - }; - if (c.attribution) { - options.attributions = [c.attribution]; - } - const fields = new VectorTile({ - declutter: true, - title: c.title, - displayInLayerSwitcher: true, - minZoom, - source: new PMTilesVectorSource(options), - style: fieldStyle - }); - map.addLayer(fields); - } -} - -document.getElementById('count').innerText = count.toLocaleString(); - -const popup = document.getElementById('popup'); -const overlay = new Overlay({ - element: popup, - positioning: 'bottom-center', - stopEvent: true, -}); -map.addOverlay(overlay); - -// Add a click event listener to the map -map.on('click', event => { - const features = map.getFeaturesAtPixel(event.pixel); - const nodes = []; - for (const feature of features) { - const properties = feature.getProperties(); - const isCollection = Array.isArray(properties.bbox); - - let content = ''; - content += `

${properties.title || properties.id || 'unknown identifier'}

`; - if (isCollection) { - content += `

`; - if (properties.source) { - content += `

`; - } - if (properties.pmtiles) { - content += `

`; - } - else { - content += `No visualization available for this dataset.`; - } - content += `

`; - } - content += `'; - - const container = document.createElement('section'); - container.innerHTML = content; - nodes.push(container); - - const focusLink = container.getElementsByClassName("focus"); - if (focusLink.length > 0) { - focusLink[0].addEventListener('click', () => { - overlay.setPosition(undefined); - map.getView().fit(feature.getGeometry().getExtent(), { duration: 500 }); - }); - } - } - overlay.setPosition(nodes.length > 0 ? event.coordinate : undefined); - popup.replaceChildren(...nodes); -}); - -// Change mouse cursor when hovering over features -map.on('pointermove', e => { - const hit = map.hasFeatureAtPixel(e.pixel); - map.getTargetElement().style.cursor = hit ? 'pointer' : ''; -}); - -map.on('moveend', () => { - const showBBox = map.getView().getZoom() < minZoom; - document.getElementById('hint').style.display = showBBox ? 'block' : 'none'; -}); +const m = new FiboaMap(); +m.addCollections(collections); diff --git a/map/map.js b/map/map.js new file mode 100644 index 0000000..99218a8 --- /dev/null +++ b/map/map.js @@ -0,0 +1,188 @@ +import "./style.css"; +import { Feature, Map, Overlay, View } from "ol"; +import { fromExtent } from "ol/geom/Polygon"; +import TileLayer from 'ol/layer/WebGLTile.js'; +import VectorTile from "ol/layer/VectorTile"; +import VectorLayer from "ol/layer/Vector"; +import { useGeographic } from 'ol/proj'; +import { OSM, Vector } from "ol/source"; +import LayerSwitcher from "ol-ext/control/LayerSwitcher" +import { PMTilesVectorSource } from "ol-pmtiles"; +import Stroke from "ol/style/Stroke"; +import Fill from "ol/style/Fill"; +import Style from "ol/style/Style"; + +useGeographic(); +export class FiboaMap { + minZoom = 7; + bboxStroke = new Stroke({ + color: 'rgb(0, 0, 0)', + width: 1 + }); + bboxRedStroke = new Stroke({ + color: 'rgb(255, 0, 0)', + width: 1 + }); + bboxFill = new Fill({ + color: 'rgba(0, 0, 0, 0.1)' + }); + fieldStyle = new Style({ + stroke: new Stroke({ + color: 'rgb(0, 165, 255)', + width: 1 + }), + fill: new Fill({ + color: 'rgba(0, 165, 255, 0.1)' + }) + }); + + constructor() { + this.map = new Map({ + target: 'map', + layers: [ + new TileLayer({ + displayInLayerSwitcher: false, + source: new OSM() + }) + ], + view: new View({ + projection: 'EPSG:3857', + center: [0, 20], + zoom: 1 + }), + controls: [ + new LayerSwitcher({ + show_progress: true, + extent: true + }) + ] + }) + const popup = document.getElementById('popup'); + this.overlay = new Overlay({ + element: popup, + positioning: 'bottom-center', + stopEvent: true, + }); + this.map.addOverlay(this.overlay); + for (const method of ["click", "pointermove", "moveend"]) { + this.map.on(method, this[method].bind(this)) + } + } + addBboxLayers(collections) { + for (const c of collections) { + const bboxes = c.bbox.length > 1 ? c.bbox.slice(1) : c.bbox; + const features = []; + for (const bbox of bboxes) { + const feature = new Feature(fromExtent(bbox)); + feature.setProperties(c); + features.push(feature); + } + const bboxLayer = new VectorLayer({ + title: c.title, + displayInLayerSwitcher: false, + maxZoom: c.pmtiles ? this.minZoom : undefined, + source: new Vector({features}), + style: new Style({ + stroke: c.pmtiles ? this.bboxStroke : this.bboxRedStroke, + fill: this.bboxFill + }) + }); + this.map.addLayer(bboxLayer); + } + } + addPMTilesLayer(c, options) { + const fields = new VectorTile({ + declutter: true, + title: c.title, + displayInLayerSwitcher: true, + minZoom: this.minZoom, + source: new PMTilesVectorSource(options), + style: this.fieldStyle + }); + this.map.addLayer(fields); + } + addPmtilesLayers(collections) { + for (const c of collections) { + if (!c.pmtiles) continue; + + const options = { + url: c.pmtiles + }; + if (c.attribution) { + options.attributions = [c.attribution]; + } + this.addPMTilesLayer(c, options); + } + } + addCollections(collections) { + this.addBboxLayers(collections); + this.addPmtilesLayers(collections); + const count = collections.reduce((acc, c) => acc + c.count || 0, 0); + document.getElementById('count').innerText = count.toLocaleString(); + } + // Add a click event listener to the map + click(event) { + const map = this.map; + const features = map.getFeaturesAtPixel(event.pixel); + const nodes = []; + for (const feature of features) { + const properties = feature.getProperties(); + const isCollection = Array.isArray(properties.bbox); + if (nodes.length) nodes.push(document.createElement('hr')); + let content = `

${properties.title || properties.id || 'unknown identifier'}

`; + if (isCollection) { + let lcontent = ""; + if (properties.source) { + lcontent+= `
`; + } + if (properties.pmtiles) { + lcontent += `

`; + } else { + lcontent += `No visualization available for this dataset.`; + } + content += `

${lcontent}

`; + } + content += ''; + + const container = document.createElement('section'); + container.innerHTML = content; + nodes.push(container); + + const focusLink = container.getElementsByClassName("focus"); + if (focusLink.length > 0) { + focusLink[0].addEventListener('click', () => { + this.overlay.setPosition(undefined); + map.getView().fit(feature.getGeometry().getExtent(), { duration: 500 }); + }); + } + } + this.overlay.setPosition(nodes.length > 0 ? event.coordinate : undefined); + popup.replaceChildren(...nodes); + } + + // Change mouse cursor when hovering over features + pointermove(e) { + const hit = this.map.hasFeatureAtPixel(e.pixel); + this.map.getTargetElement().style.cursor = hit ? 'pointer' : ''; + } + + moveend(e) { + const showBBox = this.map.getView().getZoom() < this.minZoom; + document.getElementById('hint').style.display = showBBox ? 'block' : 'none'; + } +} diff --git a/map/package-lock.json b/map/package-lock.json index 82bf5af..70aafbf 100644 --- a/map/package-lock.json +++ b/map/package-lock.json @@ -8,82 +8,15 @@ "name": "map", "version": "1.0.0", "dependencies": { + "lodash.debounce": "^4.0.8", "ol": "^10.0.0", "ol-ext": "^4.0.22", - "ol-pmtiles": "^1.0.2" + "ol-pmtiles": "^2.0.2" }, "devDependencies": { "vite": "^6.2.4" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", @@ -101,383 +34,15 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@petamoriken/float16": { "version": "3.8.7", "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.7.tgz", "integrity": "sha512-/Ri4xDDpe12NT6Ex/DRgHzLlobiQXEW/hmG08w1wj/YU7hLemk97c+zHQFp0iZQ9r7YqgLEXZR2sls4HxBf9NA==" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz", - "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz", - "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz", - "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", + "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", "cpu": [ "arm64" ], @@ -488,244 +53,6 @@ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz", - "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz", - "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz", - "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz", - "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz", - "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz", - "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz", - "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz", - "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz", - "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz", - "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz", - "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz", - "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz", - "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz", - "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz", - "integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.38.0.tgz", - "integrity": "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.38.0.tgz", - "integrity": "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -733,19 +60,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/geojson": { - "version": "7946.0.14", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", - "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" - }, - "node_modules/@types/leaflet": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", - "integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==", - "dependencies": { - "@types/geojson": "*" - } - }, "node_modules/@types/rbush": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-3.0.4.tgz", @@ -830,7 +144,8 @@ "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" }, "node_modules/fsevents": { "version": "2.3.3", @@ -838,6 +153,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -869,6 +185,12 @@ "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -915,11 +237,12 @@ } }, "node_modules/ol-pmtiles": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ol-pmtiles/-/ol-pmtiles-1.0.2.tgz", - "integrity": "sha512-+2itEeTcOk6RWikH2/cWIvv2mFW0empLaCibon65e1kyOEzB+zHIqF3eKa15yyznV8r9K0wfx9S3aG6ceXL0hQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ol-pmtiles/-/ol-pmtiles-2.0.2.tgz", + "integrity": "sha512-UVGEHoSi8mCGiDUyfqZmx+lbDwXtSwpEeGNQAzIZskEJ8tQeOGFcezisRTjJc1wu5KnT7ckpDLQePqA6LUi/ow==", + "license": "BSD-3-Clause", "dependencies": { - "pmtiles": "^3.1.0" + "pmtiles": "^4.3.0" }, "peerDependencies": { "ol": ">=9.0.0" @@ -954,12 +277,12 @@ "license": "ISC" }, "node_modules/pmtiles": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-3.1.0.tgz", - "integrity": "sha512-6JvgAQ8gElP1Ilg6ILM4KqleeKS+QcwpW8PXqhPWjRFmqF42yyUJ8sP3dZHQXm+G0HYXuw1kGlMTdVEs583pCQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-4.3.0.tgz", + "integrity": "sha512-wnzQeSiYT/MyO63o7AVxwt7+uKqU0QUy2lHrivM7GvecNy0m1A4voVyGey7bujnEW5Hn+ZzLdvHPoFaqrOzbPA==", + "license": "BSD-3-Clause", "dependencies": { - "@types/leaflet": "^1.9.8", - "fflate": "^0.8.0" + "fflate": "^0.8.2" } }, "node_modules/postcss": { @@ -1029,9 +352,9 @@ } }, "node_modules/rollup": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.38.0.tgz", - "integrity": "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", + "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", "dev": true, "license": "MIT", "dependencies": { @@ -1045,26 +368,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.38.0", - "@rollup/rollup-android-arm64": "4.38.0", - "@rollup/rollup-darwin-arm64": "4.38.0", - "@rollup/rollup-darwin-x64": "4.38.0", - "@rollup/rollup-freebsd-arm64": "4.38.0", - "@rollup/rollup-freebsd-x64": "4.38.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.38.0", - "@rollup/rollup-linux-arm-musleabihf": "4.38.0", - "@rollup/rollup-linux-arm64-gnu": "4.38.0", - "@rollup/rollup-linux-arm64-musl": "4.38.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.38.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.38.0", - "@rollup/rollup-linux-riscv64-gnu": "4.38.0", - "@rollup/rollup-linux-riscv64-musl": "4.38.0", - "@rollup/rollup-linux-s390x-gnu": "4.38.0", - "@rollup/rollup-linux-x64-gnu": "4.38.0", - "@rollup/rollup-linux-x64-musl": "4.38.0", - "@rollup/rollup-win32-arm64-msvc": "4.38.0", - "@rollup/rollup-win32-ia32-msvc": "4.38.0", - "@rollup/rollup-win32-x64-msvc": "4.38.0", + "@rollup/rollup-android-arm-eabi": "4.39.0", + "@rollup/rollup-android-arm64": "4.39.0", + "@rollup/rollup-darwin-arm64": "4.39.0", + "@rollup/rollup-darwin-x64": "4.39.0", + "@rollup/rollup-freebsd-arm64": "4.39.0", + "@rollup/rollup-freebsd-x64": "4.39.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", + "@rollup/rollup-linux-arm-musleabihf": "4.39.0", + "@rollup/rollup-linux-arm64-gnu": "4.39.0", + "@rollup/rollup-linux-arm64-musl": "4.39.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", + "@rollup/rollup-linux-riscv64-gnu": "4.39.0", + "@rollup/rollup-linux-riscv64-musl": "4.39.0", + "@rollup/rollup-linux-s390x-gnu": "4.39.0", + "@rollup/rollup-linux-x64-gnu": "4.39.0", + "@rollup/rollup-linux-x64-musl": "4.39.0", + "@rollup/rollup-win32-arm64-msvc": "4.39.0", + "@rollup/rollup-win32-ia32-msvc": "4.39.0", + "@rollup/rollup-win32-x64-msvc": "4.39.0", "fsevents": "~2.3.2" } }, @@ -1079,9 +402,9 @@ } }, "node_modules/vite": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz", - "integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==", + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", + "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/map/package.json b/map/package.json index 589635f..f65f0ce 100644 --- a/map/package.json +++ b/map/package.json @@ -11,8 +11,9 @@ "vite": "^6.2.4" }, "dependencies": { + "lodash.debounce": "^4.0.8", "ol": "^10.0.0", "ol-ext": "^4.0.22", - "ol-pmtiles": "^1.0.2" + "ol-pmtiles": "^2.0.2" } } diff --git a/map/prepare.js b/map/prepare.js index bf55906..bf3dfbe 100644 --- a/map/prepare.js +++ b/map/prepare.js @@ -1,4 +1,5 @@ const fs = require('fs'); +const crypto = require('crypto'); const data = fs.readFileSync('../stac/catalog.json', 'utf8'); const json = JSON.parse(data); @@ -28,7 +29,8 @@ Promise.allSettled(promises) title: (c.title || "").replace('Field boundaries for ', ''), attribution: c.attribution, bbox: c.extent.spatial.bbox, - source: c.source + source: c.source, + fiboa_extensions: c.fiboa_extensions, }; const pmtiles = c.links.find(l => l.rel === 'pmtiles'); @@ -51,3 +53,13 @@ Promise.allSettled(promises) fs.writeFileSync('sources.js', `export default ${JSON.stringify(results, null, 2)}`); }) .catch(console.error); + +function strToColor(str) { + return '#' + crypto.createHash('md5').update(str).digest('hex').substring(0, 6); +} +const hcat = fs.readFileSync('../code/hcat/HCAT3.csv', 'utf8'); +const hcatMapping = hcat.split('\n').slice(1).map((line) => { + const [name, code] = line.split(','); + return { name, code, color: strToColor(code || "") }; +}); +fs.writeFileSync('crop/codes.js', `export const hcat = ${JSON.stringify(hcatMapping, null, 2)}`); diff --git a/map/style.css b/map/style.css index 54317e0..caaac6f 100644 --- a/map/style.css +++ b/map/style.css @@ -97,4 +97,54 @@ form { .ol-popup ul li { list-style: none; margin: 0 0 0.25rem; -} \ No newline at end of file +} + +.ol-crop-legend { + right: .5em; + bottom: .5em; + + background-color: rgba(255, 255, 255, 0.85); + border: 1px solid #ccc; + padding: 10px; + font-size: 12px; + line-height: 1.5; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); +} + +.ol-crop-legend .legend-title { + font-weight: bold; + margin-bottom: 5px; +} + +.ol-crop-legend .legend-level { + display: inline-block; + font-weight: normal; + margin-bottom: 5px; + padding-left: 5px; + padding-right: 5px; + border: 1px black; +} + +.ol-crop-legend .legend-level.active { + background-color: lightblue; +} + +.ol-crop-legend .legend-list { + list-style: none; + margin: 0; + padding: 0; +} + +.ol-crop-legend .legend-list li { + display: flex; + align-items: center; + margin-bottom: 5px; +} + +.ol-crop-legend .legend-color { + width: 20px; + height: 20px; + flex-shrink: 0; + margin-right: 10px; + border: 1px solid #000000; +} diff --git a/map/vite.config.js b/map/vite.config.js index 8221856..f98a81e 100644 --- a/map/vite.config.js +++ b/map/vite.config.js @@ -1,6 +1,17 @@ +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + export default { base: '/map', build: { sourcemap: true, - } + rollupOptions: { + input: { + main: resolve(__dirname, './index.html'), + crop: resolve(__dirname, './crop/index.html'), + }, + }, + }, }