Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Replace osmtogeojson #11438

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile.frontend
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
ARG USER_UID=1000
ARG USER_GID=1000

FROM node:22.9.0 AS builder
FROM node:lts AS builder
ARG USER_UID
ARG USER_GID
RUN usermod --uid $USER_UID node && \
Expand Down
2 changes: 1 addition & 1 deletion gulpfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ export function copyJs() {
"./node_modules/foundation-sites/js/vendor/*.js",
"./node_modules/foundation-sites/js/foundation.js",
"./node_modules/papaparse/papaparse.js",
"./node_modules/osmtogeojson/osmtogeojson.js",
"./node_modules/leaflet/dist/leaflet.js",
"./node_modules/leaflet/dist/leaflet-src.esm.js",
"./node_modules/leaflet.markercluster/dist/leaflet.markercluster.js",
"./node_modules/blueimp-tmpl/js/tmpl.js",
"./node_modules/blueimp-load-image/js/load-image.all.min.js",
Expand Down
10 changes: 5 additions & 5 deletions html/js/display-map.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This file is part of Product Opener.
//
// Product Opener
// Copyright (C) 2011-2023 Association Open Food Facts
// Copyright (C) 2011-2025 Association Open Food Facts
// Contact: [email protected]
// Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France
//
Expand All @@ -18,10 +18,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

/*global L */
/*exported displayMap*/
import * as L from './leaflet-src.esm.js';
import { MarkerClusterGroup } from './leaflet.markercluster.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to test it locally, but I'm getting an error when displaying search results on a map:

display-map.js:22 Uncaught SyntaxError: The requested module './leaflet.markercluster.js' does not provide an export named 'MarkerClusterGroup' (at display-map.js:22:10)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'll take a look


function displayMap(containerId, pointers) {
export function displayMap(containerId, pointers) {
const map = L.map(containerId, { maxZoom: 12 });

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
Expand All @@ -30,7 +30,7 @@ function displayMap(containerId, pointers) {
}).addTo(map);


const markers = new L.MarkerClusterGroup({ singleMarkerMode: true });
const markers = new MarkerClusterGroup({ singleMarkerMode: true });
const layers = [];

for (const pointer of pointers) {
Expand Down
201 changes: 124 additions & 77 deletions html/js/display-tag.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This file is part of Product Opener.
//
// Product Opener
// Copyright (C) 2011-2023 Association Open Food Facts
// Copyright (C) 2011-2025 Association Open Food Facts
// Contact: [email protected]
// Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France
//
Expand All @@ -18,15 +18,69 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

/*global L osmtogeojson*/
/*exported displayMap*/
import * as L from './leaflet-src.esm.js';

let map;
function ensureMapIsDisplayed() {
if (map) {
return;
/* (c) mapstertech https://github.com/mapstertech/mapster-right-hand-rule-fixer/blob/d374e4153ba26c2100b509f59e5a5fe616e267dd/lib/rewind-browser.js */
class GeoJSONRewind {

static rewindRing(ring, dir) {
let area = 0,
err = 0;
// eslint-disable-next-line no-plusplus
for (let i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
const k = (ring[i][0] - ring[j][0]) * (ring[j][1] + ring[i][1]);
const m = area + k;
err += Math.abs(area) >= Math.abs(k) ? area - m + k : k - m + area;
area = m;
}
// eslint-disable-next-line no-mixed-operators
if (area + err >= 0 !== Boolean(dir)) {
ring.reverse();
}
}

rewindRings(rings, outer) {
if (rings.length === 0) {
return;
}

this.rewindRing(rings[0], outer);
for (let i = 1; i < rings.length; i++) {
this.rewindRing(rings[i], !outer);
}
}

rewind(gj, outer) {
const type = gj?.type;
let i;

if (type === 'FeatureCollection') {
for (i = 0; i < gj.features.length; i++) {
this.rewind(gj.features[i], outer);
}

} else if (type === 'GeometryCollection') {
for (i = 0; i < gj.geometries.length; i++) {
this.rewind(gj.geometries[i], outer);
}

} else if (type === 'Feature') {
this.rewind(gj.geometry, outer);

} else if (type === 'Polygon') {
this.rewindRings(gj.coordinates, outer);

} else if (type === 'MultiPolygon') {
for (i = 0; i < gj.coordinates.length; i++) {
this.rewindRings(gj.coordinates[i], outer);
}
}

return gj;
}
}

function createLeafletMap() {
const tagDescription = document.getElementById('tag_description');
if (tagDescription) {
tagDescription.classList.remove('large-12');
Expand All @@ -38,12 +92,14 @@ function ensureMapIsDisplayed() {
tagMap.style.display = '';
}

map = L.map('container');
const map = L.map('container');

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);

return map;
}

function fitBoundsToAllLayers(mapToUpdate) {
Expand All @@ -58,94 +114,85 @@ function fitBoundsToAllLayers(mapToUpdate) {
mapToUpdate.fitBounds(latlngbounds);
}

function runCallbackOnJson(callback) {
ensureMapIsDisplayed();
callback(map);
}

function addWikidataObjectToMap(id) {
getOpenStreetMapFromWikidata(id, function (data) {
const bindings = data.results.bindings;
if (bindings.length === 0) {
return;
}
async function addWikidataObjectToMap(id) {
const wikidata_result = await getOpenStreetMapFromWikidata(id);
const bindings = wikidata_result.results.bindings;
if (bindings.length === 0) {
return;
}

const binding = bindings[0];
const relationId = binding.OpenStreetMap_Relations_ID.value;
if (!relationId) {
return;
}
const binding = bindings[0];
const relationId = binding.OpenStreetMap_Relations_ID.value;
if (!relationId) {
return;
}

getGeoJsonFromOsmRelation(relationId, function (geoJson) {
if (geoJson) {
runCallbackOnJson(function (mapToUpdate) {
L.geoJSON(geoJson).addTo(mapToUpdate);
fitBoundsToAllLayers(mapToUpdate);
});
}
});
});
const geoJson = await getGeoJsonFromOsmRelation(relationId);
if (geoJson) {
const map = createLeafletMap();
L.geoJSON(geoJson).addTo(map);
fitBoundsToAllLayers(map);
}
}

function getOpenStreetMapFromWikidata(id, callback) {
const endpointUrl = 'https://query.wikidata.org/sparql',
sparqlQuery = "SELECT ?OpenStreetMap_Relations_ID WHERE {\n" +
" wd:" + id + " wdt:P402 ?OpenStreetMap_Relations_ID.\n" +
"}",
settings = {
headers: { Accept: 'application/sparql-results+json' },
data: { query: sparqlQuery }
};

$.ajax(endpointUrl, settings).then(callback);
}
async function getOpenStreetMapFromWikidata(id) {
const endpointUrl = 'https://query.wikidata.org/sparql';
const sparqlQuery = `SELECT ?OpenStreetMap_Relations_ID WHERE {
wd:${id} wdt:P402 ?OpenStreetMap_Relations_ID.
}`;
const settings = {
headers: { Accept: 'application/sparql-results+json' }
};

const response = await fetch(`${endpointUrl}?query=${encodeURIComponent(sparqlQuery)}`, settings);
const data = await response.json();

function getOsmDataFromOverpassTurbo(id, callback) {
$.ajax('https://overpass-api.de/api/interpreter?data=relation%28' + id + '%29%3B%0A%28._%3B%3E%3B%29%3B%0Aout%3B').then(callback);
return data;
}

function getGeoJsonFromOsmRelation(id, callback) {
getOsmDataFromOverpassTurbo(id, function (xml) {
callback(osmtogeojson(xml));
});
async function getGeoJsonFromOsmRelation(id) {
const response = await fetch(`https://polygons.openstreetmap.fr/get_geojson.py?params=0&id=${encodeURIComponent(id)}`);
const data = await response.json();
const geoJsonRewind = new GeoJSONRewind();

return geoJsonRewind.rewind(data);
}

function displayPointers(pointers) {
runCallbackOnJson(function (actualMap) {
const markers = [];
for (const pointer of pointers) {
let coordinates;

// If pointer is an array, it just contains (lat, lng) geo coordinates
if (Array.isArray(pointer)) {
coordinates = pointer;
}
// Otherwise we have a structured object
// e.g. from a map element of a knowledge panel
else {
coordinates = [pointer.geo.lat, pointer.geo.lng];
}

const marker = new L.marker(coordinates);
markers.push(marker);
const map = createLeafletMap();
const markers = [];
for (const pointer of pointers) {
let coordinates;

// If pointer is an array, it just contains (lat, lng) geo coordinates
if (Array.isArray(pointer)) {
coordinates = pointer;
}

if (markers.length > 0) {
L.featureGroup(markers).addTo(actualMap);
fitBoundsToAllLayers(actualMap);
actualMap.setZoom(8);
// Otherwise we have a structured object
// e.g. from a map element of a knowledge panel
else {
coordinates = [pointer.geo.lat, pointer.geo.lng];
}
});

const marker = new L.marker(coordinates);
markers.push(marker);
}

if (markers.length > 0) {
L.featureGroup(markers).addTo(map);
fitBoundsToAllLayers(map);
map.setZoom(8);
}
}

function displayMap(pointers, wikidataObjects) {
export async function displayMap(pointers, wikidataObjects) {
if (pointers.length > 0) {
displayPointers(pointers);
}

for (const wikidataObject of wikidataObjects) {
if (wikidataObject !== null) {
addWikidataObjectToMap(wikidataObject);
await addWikidataObjectToMap(wikidataObject); // eslint-disable-line no-await-in-loop
}
}
}
}
Loading
Loading