From 1de4042d91f48eb0a98c3432fe896f33448ee029 Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Mon, 29 Jan 2024 16:00:36 +0100 Subject: [PATCH] Handle path and rects in map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are many installations on the map that are drawn with rect instead of path. I could transform them with Inkscape, but i finally found out about DOMMatrix, that helps me a lot to convert points, as it can parse the values of the `transform` attribute. I also saw that SVGGraphicElements has getCTM() and getScreenCTM(), but neither of them worked for me: the resulting transformation was “shrunk” on the map (i.e., like it was scaled down). --- demo/demo.sql | 88 ++++++++++++++++++++++++++++++----- web/static/map.js | 116 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 159 insertions(+), 45 deletions(-) diff --git a/demo/demo.sql b/demo/demo.sql index debbd6f..c6a8412 100644 --- a/demo/demo.sql +++ b/demo/demo.sql @@ -1503,19 +1503,85 @@ values (52, 72, 'Juli Verd', current_date + interval '23 days', current_date + i alter table amenity alter column amenity_id restart with 132; -select add_amenity(52, 'edifici-camping', 'Edifici Càmping', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

'); +select add_amenity(52, 'camp-esport', 'Camp Esport', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

'); +select add_amenity(52, 'botiga', 'Botiga', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

'); +select add_amenity(52, 'recepcio', 'Recepció', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

'); +select add_amenity(52, 'bar', 'Bar', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

'); +select add_amenity(52, 'edifici_serveis', 'Edifici Serveis', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

'); +select add_amenity(52, 'piscina', 'Piscina', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

'); +select add_amenity(52, 'parc', 'Parc', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

', '

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec scelerisque lorem vestibulum enim sollicitudin ornare. Aliquam egestas pretium porttitor. Donec iaculis tempus est, id lobortis risus semper vel. Maecenas ut imperdiet neque. Donec mattis purus felis, vitae interdum risus egestas pharetra. Vestibulum dui neque, condimentum ultrices erat sed, fringilla pharetra ante. Maecenas hendrerit neque mattis risus consectetur euismod. Cras urna metus, bibendum a neque sed, pharetra commodo magna.

'); -select add_amenity_carousel_slide(52, 'edifici-camping', 80, 'Llegenda'); -select add_amenity_carousel_slide(52, 'edifici-camping', 81, 'Llegenda'); -select add_amenity_carousel_slide(52, 'edifici-camping', 82, 'Llegenda'); -select add_amenity_carousel_slide(52, 'edifici-camping', 83, 'Llegenda'); -select add_amenity_carousel_slide(52, 'edifici-camping', 84, 'Llegenda'); -select add_amenity_carousel_slide(52, 'edifici-camping', 85, 'Llegenda'); +select add_amenity_carousel_slide(52, 'camp-esport', 80, 'Llegenda'); +select add_amenity_carousel_slide(52, 'camp-esport', 81, 'Llegenda'); +select add_amenity_carousel_slide(52, 'camp-esport', 82, 'Llegenda'); +select add_amenity_carousel_slide(52, 'camp-esport', 83, 'Llegenda'); +select add_amenity_carousel_slide(52, 'camp-esport', 84, 'Llegenda'); +select add_amenity_carousel_slide(52, 'camp-esport', 85, 'Llegenda'); +select add_amenity_carousel_slide(52, 'botiga', 80, 'Llegenda'); +select add_amenity_carousel_slide(52, 'botiga', 81, 'Llegenda'); +select add_amenity_carousel_slide(52, 'botiga', 82, 'Llegenda'); +select add_amenity_carousel_slide(52, 'botiga', 83, 'Llegenda'); +select add_amenity_carousel_slide(52, 'botiga', 84, 'Llegenda'); +select add_amenity_carousel_slide(52, 'botiga', 85, 'Llegenda'); +select add_amenity_carousel_slide(52, 'recepcio', 80, 'Llegenda'); +select add_amenity_carousel_slide(52, 'recepcio', 81, 'Llegenda'); +select add_amenity_carousel_slide(52, 'recepcio', 82, 'Llegenda'); +select add_amenity_carousel_slide(52, 'recepcio', 83, 'Llegenda'); +select add_amenity_carousel_slide(52, 'recepcio', 84, 'Llegenda'); +select add_amenity_carousel_slide(52, 'recepcio', 85, 'Llegenda'); +select add_amenity_carousel_slide(52, 'bar', 80, 'Llegenda'); +select add_amenity_carousel_slide(52, 'bar', 81, 'Llegenda'); +select add_amenity_carousel_slide(52, 'bar', 82, 'Llegenda'); +select add_amenity_carousel_slide(52, 'bar', 83, 'Llegenda'); +select add_amenity_carousel_slide(52, 'bar', 84, 'Llegenda'); +select add_amenity_carousel_slide(52, 'bar', 85, 'Llegenda'); +select add_amenity_carousel_slide(52, 'edifici_serveis', 80, 'Llegenda'); +select add_amenity_carousel_slide(52, 'edifici_serveis', 81, 'Llegenda'); +select add_amenity_carousel_slide(52, 'edifici_serveis', 82, 'Llegenda'); +select add_amenity_carousel_slide(52, 'edifici_serveis', 83, 'Llegenda'); +select add_amenity_carousel_slide(52, 'edifici_serveis', 84, 'Llegenda'); +select add_amenity_carousel_slide(52, 'edifici_serveis', 85, 'Llegenda'); +select add_amenity_carousel_slide(52, 'piscina', 80, 'Llegenda'); +select add_amenity_carousel_slide(52, 'piscina', 81, 'Llegenda'); +select add_amenity_carousel_slide(52, 'piscina', 82, 'Llegenda'); +select add_amenity_carousel_slide(52, 'piscina', 83, 'Llegenda'); +select add_amenity_carousel_slide(52, 'piscina', 84, 'Llegenda'); +select add_amenity_carousel_slide(52, 'piscina', 85, 'Llegenda'); +select add_amenity_carousel_slide(52, 'parc', 80, 'Llegenda'); +select add_amenity_carousel_slide(52, 'parc', 81, 'Llegenda'); +select add_amenity_carousel_slide(52, 'parc', 82, 'Llegenda'); +select add_amenity_carousel_slide(52, 'parc', 83, 'Llegenda'); +select add_amenity_carousel_slide(52, 'parc', 84, 'Llegenda'); +select add_amenity_carousel_slide(52, 'parc', 85, 'Llegenda'); -select add_amenity_feature(52, 'edifici-camping', 'person', 'Máx. 6 pers.'); -select add_amenity_feature(52, 'edifici-camping', 'area', 'de 55 a 65 m²'); -select add_amenity_feature(52, 'edifici-camping', 'electricity', 'Electricitat'); -select add_amenity_feature(52, 'edifici-camping', 'shower', 'Accés als serveis'); +select add_amenity_feature(52, 'camp-esport', 'person', 'Máx. 6 pers.'); +select add_amenity_feature(52, 'camp-esport', 'area', 'de 55 a 65 m²'); +select add_amenity_feature(52, 'camp-esport', 'electricity', 'Electricitat'); +select add_amenity_feature(52, 'camp-esport', 'shower', 'Accés als serveis'); +select add_amenity_feature(52, 'botiga', 'person', 'Máx. 6 pers.'); +select add_amenity_feature(52, 'botiga', 'area', 'de 55 a 65 m²'); +select add_amenity_feature(52, 'botiga', 'electricity', 'Electricitat'); +select add_amenity_feature(52, 'botiga', 'shower', 'Accés als serveis'); +select add_amenity_feature(52, 'recepcio', 'person', 'Máx. 6 pers.'); +select add_amenity_feature(52, 'recepcio', 'area', 'de 55 a 65 m²'); +select add_amenity_feature(52, 'recepcio', 'electricity', 'Electricitat'); +select add_amenity_feature(52, 'recepcio', 'shower', 'Accés als serveis'); +select add_amenity_feature(52, 'bar', 'person', 'Máx. 6 pers.'); +select add_amenity_feature(52, 'bar', 'area', 'de 55 a 65 m²'); +select add_amenity_feature(52, 'bar', 'electricity', 'Electricitat'); +select add_amenity_feature(52, 'bar', 'shower', 'Accés als serveis'); +select add_amenity_feature(52, 'edifici_serveis', 'person', 'Máx. 6 pers.'); +select add_amenity_feature(52, 'edifici_serveis', 'area', 'de 55 a 65 m²'); +select add_amenity_feature(52, 'edifici_serveis', 'electricity', 'Electricitat'); +select add_amenity_feature(52, 'edifici_serveis', 'shower', 'Accés als serveis'); +select add_amenity_feature(52, 'piscina', 'person', 'Máx. 6 pers.'); +select add_amenity_feature(52, 'piscina', 'area', 'de 55 a 65 m²'); +select add_amenity_feature(52, 'piscina', 'electricity', 'Electricitat'); +select add_amenity_feature(52, 'piscina', 'shower', 'Accés als serveis'); +select add_amenity_feature(52, 'parc', 'person', 'Máx. 6 pers.'); +select add_amenity_feature(52, 'parc', 'area', 'de 55 a 65 m²'); +select add_amenity_feature(52, 'parc', 'electricity', 'Electricitat'); +select add_amenity_feature(52, 'parc', 'shower', 'Accés als serveis'); insert into legal_text (company_id, slug, name, content) values (52, 'reservation', 'Normes i condicions de cancel·lació', '

Termes de canceŀlació

(La sol·licitud es farà sempre per escrit pel client.)

') diff --git a/web/static/map.js b/web/static/map.js index 8032a84..f15b281 100644 --- a/web/static/map.js +++ b/web/static/map.js @@ -76,11 +76,32 @@ }); } - // XXX: this is from the “parceles” layer. - const ctm = [1, 0, 0, 1, 83.2784, 66.1766]; + function getPath(el) { + if (!el) { + return el; + } + switch (el.localName) { + case 'path': + case 'rect': + return el; + case 'g': + return getPath(el.firstElementChild); + default: + console.warn('unhandled element type', el.localName); + return null + } + } - function transform(x, y) { - return [ctm[0] * x + ctm[2] * y + ctm[4], ctm[1] * x + ctm[3] * y + ctm[5]]; + function getCTM(ctm, el) { + if (el.localName === 'svg') { + return ctm; + } + const transform = el.getAttribute('transform'); + if (!transform) { + return getCTM(ctm, el.parentElement); + } + const matrix = new DOMMatrix(transform); + return getCTM(matrix.multiply(ctm), el.parentElement); } function setupFeatures(prefix, baseURI) { @@ -89,37 +110,64 @@ if (!label) { continue; } - const path = campsite.firstElementChild; - const commands = parse(path.getAttribute('d')); - const points = []; - const p0 = [0, 0]; - for (const cmd of commands) { - switch (cmd[0]) { - case 'M': - case 'L': - const cmdM = transform(cmd[1], cmd[2]); - p0[0] = cmdM[0]; - p0[1] = latLngBounds.getNorth() - cmdM[1]; - break; - case 'm': - case 'l': - p0[0] += cmd[1]; - p0[1] -= cmd[2]; - break; - case 'C': - const cmdC = transform(cmd[5], cmd[6]); - p0[0] = cmdC[0]; - p0[1] = latLngBounds.getNorth() - cmdC[1]; - break; - case 'Z': - case 'z': - continue; - default: - console.error(cmd); - } - points.push([p0[1], p0[0]]); + const path = getPath(campsite); + if (!path) { + console.warn(campsite, 'has no path'); + continue; + } + const ctm = getCTM(new DOMMatrix([1, 0, 0, 1, 0, 0]), path); + const points = []; + const p0 = new DOMPoint(); + + function setP0(x, y) { + p0.x = x; + p0.y = y; + const p1 = p0.matrixTransform(ctm); + p0.x = p1.x; + p0.y = latLngBounds.getNorth() - p1.y; + } + + switch (path.localName) { + case 'rect': + const x = parseFloat(path.getAttribute('x')); + const y = parseFloat(path.getAttribute('y')); + const w = parseFloat(path.getAttribute('width')); + const h = parseFloat(path.getAttribute('height')); + setP0(x, y); + points.push([p0.y, p0.x]); + setP0(x + w, y); + points.push([p0.y, p0.x]); + setP0(x + w, y + h); + points.push([p0.y, p0.x]); + setP0(x, y + h); + points.push([p0.y, p0.x]); + break; + case 'path': + const commands = parse(path.getAttribute('d')); + for (const cmd of commands) { + switch (cmd[0]) { + case 'M': + case 'L': + setP0(cmd[1], cmd[2]); + break; + case 'm': + case 'l': + p0.x += cmd[1]; + p0.y -= cmd[2]; + break; + case 'C': + setP0(cmd[5], cmd[6]); + break; + case 'Z': + case 'z': + continue; + default: + console.error(cmd); + } + points.push([p0.y, p0.x]); + } + break; } - console.log(points); const feature = L.polygon(points, {color: 'transparent'}).addTo(map); feature.on({ mouseover: highlightAccommodation,