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}) => `
+
+ -
+
+ ${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 += ``;
- for (const key in properties) {
- if (typeof key !== 'undefined' && !['geometry', 'bbox', 'title', 'pmtiles', 'source'].includes(key)) {
-
- content += `- ${key}: `
- if (Array.isArray(properties[key])) {
- content += properties[key].join(', ');
- }
- else if (typeof properties[key] === 'number') {
- content += properties[key].toLocaleString();
- }
- else {
- content += properties[key];
- }
- 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 += '';
+ for (const key in properties) {
+ let lcontent = "";
+ if (typeof key !== 'undefined' && !['geometry', 'bbox', 'title', 'pmtiles', 'source'].includes(key)) {
+ lcontent += `- ${key}:`
+ if (Array.isArray(properties[key])) {
+ lcontent += properties[key].join(', ');
+ } else if (typeof properties[key] === 'number') {
+ lcontent += properties[key].toLocaleString();
+ } else {
+ lcontent += properties[key];
+ }
+ 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'),
+ },
+ },
+ },
}