Skip to content

Commit 52b897e

Browse files
authored
Merge pull request #4208 from rldhont/fix-proj-axis-orientation-neu
[Bugfix] JS: Axis orientation projection detection
2 parents 9148c40 + a115c70 commit 52b897e

File tree

9 files changed

+5460
-6
lines changed

9 files changed

+5460
-6
lines changed

assets/src/modules/Lizmap.js

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import Permalink from './Permalink.js';
2626
import Search from './Search.js';
2727

2828
import WMSCapabilities from 'ol/format/WMSCapabilities.js';
29-
import { transform as transformOL, transformExtent as transformExtentOL, get as getProjection } from 'ol/proj.js';
29+
import {intersects as extentIntersects} from 'ol/extent.js';
30+
import { transform as transformOL, transformExtent as transformExtentOL, get as getProjection, clearAllProjections } from 'ol/proj.js';
3031
import { register } from 'ol/proj/proj4.js';
3132

3233
import proj4 from 'proj4';
@@ -46,19 +47,66 @@ export default class Lizmap {
4647
// The initialConfig has been cloned because it will be freezed
4748
this._initialConfig = new Config(structuredClone(configs.initialConfig), wmsCapabilities);
4849
this._state = new State(this._initialConfig, configs.startupFeatures);
49-
},
50-
toolbarcreated: () => {
51-
this._lizmap3 = lizMap;
5250

5351
// Register projections if unknown
5452
for (const [ref, def] of Object.entries(lizProj4)) {
55-
if (ref !== "" && !getProjection(ref)) {
53+
if (ref !== "" && !proj4.defs(ref)) {
5654
proj4.defs(ref, def);
5755
}
5856
}
59-
57+
// Register project projection if unknown
58+
const configProj = this._initialConfig.options.projection;
59+
if (configProj.ref !== "" && !proj4.defs(configProj.ref)) {
60+
proj4.defs(configProj.ref, configProj.proj4);
61+
}
62+
// About axis orientation https://proj.org/en/9.3/usage/projections.html#axis-orientation
63+
// Add CRS:84 projection, same as EPSG:4326 but with ENU axis orientation
64+
proj4.defs("CRS:84","+proj=longlat +datum=WGS84 +no_defs +type=crs");
6065
register(proj4);
66+
// Update project projection if its axis orientation is not ENU
67+
if (configProj.ref !== "") {
68+
// loop through bounding boxes of the project provided by WMS capabilities
69+
for (const bbox of wmsCapabilities.Capability.Layer.BoundingBox) {
70+
// If the BBOX CRS is not the same of the project projection, continue.
71+
if (bbox.crs !== configProj.ref) {
72+
continue;
73+
}
74+
// Get project projection
75+
const projectProj = getProjection(configProj.ref);
76+
// Check axis orientation, if it is not ENU, break, we don't have to do anything
77+
if (projectProj.getAxisOrientation() !== 'enu') {
78+
break;
79+
}
80+
// Transform geographic extent to project projection
81+
const extent = transformExtentOL(wmsCapabilities.Capability.Layer.EX_GeographicBoundingBox, 'CRS:84', bbox.crs);
82+
// Check closest coordinates
83+
if (Math.abs(extent[0] - bbox.extent[1]) < Math.abs(extent[0] - bbox.extent[0])
84+
&& Math.abs(extent[1] - bbox.extent[0]) < Math.abs(extent[1] - bbox.extent[1])
85+
&& Math.abs(extent[2] - bbox.extent[3]) < Math.abs(extent[2] - bbox.extent[2])
86+
&& Math.abs(extent[3] - bbox.extent[2]) < Math.abs(extent[3] - bbox.extent[3])) {
87+
// If inverted axis are closest, we have to update the projection definition
88+
proj4.defs(configProj.ref, configProj.proj4+' +axis=neu');
89+
clearAllProjections();
90+
register(proj4);
91+
break;
92+
}
93+
// Transform extent from project projection to CRS:84
94+
const geoExtent = transformExtentOL(bbox.extent, bbox.crs, 'CRS:84');
95+
// Check intersects between transform extent and provided extent by WMS Capapbilities
96+
if (!extentIntersects(geoExtent, wmsCapabilities.Capability.Layer.EX_GeographicBoundingBox)) {
97+
// if extents do not intersect, we have to update the projection definition
98+
proj4.defs(configProj.ref, configProj.proj4+' +axis=neu');
99+
clearAllProjections();
100+
register(proj4);
101+
break;
102+
}
103+
}
104+
}
105+
},
106+
toolbarcreated: () => {
107+
this._lizmap3 = lizMap;
61108

109+
// Register projections if unknown
62110
if (!getProjection(this.projection)) {
63111
const proj = this.config.options.projection;
64112
proj4.defs(proj.ref, proj.proj4);

lizmap/plugins/formwidget/htmlbootstrap/htmlbootstrap.formwidget.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ public function outputHeader($builder)
5252
*/
5353
public function outputFooter($builder)
5454
{
55+
// Since jelix 1.8.7, we need to add `deprecatedDeclareFormBeforeControls` builder option
56+
// because in the `outputHeader` we defined `jFormsJQ.declareForm(jFormsJQ.tForm);`
57+
// and we use `parent::outputFooter($builder);`
58+
$builder->setOptions(
59+
array(
60+
'errorDecorator' => $builder->getOption('errorDecorator'),
61+
'modal' => $builder->getOption('modal'),
62+
'deprecatedDeclareFormBeforeControls' => true,
63+
)
64+
);
5565
if ($builder->getOption('modal')) {
5666
echo '</div>';
5767
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// @ts-check
2+
const { test, expect } = require('@playwright/test');
3+
4+
test.describe('Axis Orientation', () => {
5+
6+
test('Axis Orientation NEU for EPSG:3044', async ({ page }) => {
7+
const url = '/index.php/view/map/?repository=testsrepository&project=axis_orientation_neu_3044';
8+
await page.goto(url, { waitUntil: 'networkidle' });
9+
10+
const getMapPromise = page.waitForRequest(/GetMap/);
11+
await page.getByLabel('Bundesländer').check();
12+
const getMapRequest = await getMapPromise;
13+
const getMapUrl = getMapRequest.url();
14+
expect(getMapUrl).toContain('SERVICE=WMS');
15+
expect(getMapUrl).toContain('VERSION=1.3.0');
16+
expect(getMapUrl).toContain('REQUEST=GetMap');
17+
expect(getMapUrl).toContain('FORMAT=image%2Fpng');
18+
expect(getMapUrl).toContain('TRANSPARENT=true');
19+
expect(getMapUrl).toContain('LAYERS=Bundeslander');
20+
expect(getMapUrl).toContain('CRS=EPSG%3A3044');
21+
expect(getMapUrl).toContain('STYLES=default');
22+
expect(getMapUrl).toContain('WIDTH=958');
23+
expect(getMapUrl).toContain('HEIGHT=633');
24+
expect(getMapUrl).toMatch(/BBOX=5276843.28\d+%2C-14455.54\d+%2C6114251.21\d+%2C1252901.15\d+/);
25+
26+
const getMapResponse = await getMapRequest.response();
27+
expect(getMapResponse).not.toBeNull();
28+
expect(getMapResponse?.ok()).toBe(true);
29+
expect(await getMapResponse?.headerValue('Content-Type')).toBe('image/png');
30+
// image size greater than transparent
31+
const contentLength = await getMapResponse?.headerValue('Content-Length');
32+
expect(parseInt(contentLength ? contentLength : '0')).toBeGreaterThan(5552);
33+
34+
35+
// Catch GetTile request;
36+
let GetTiles = [];
37+
await page.route('https://tile.openstreetmap.org/*/*/*.png', (route) => {
38+
const request = route.request();
39+
GetTiles.push(request.url());
40+
}, {times: 6});
41+
42+
await page.locator('#switcher-baselayer').getByRole('combobox').selectOption('OpenStreetMap');
43+
await page.waitForTimeout(1000);
44+
expect(GetTiles).toHaveLength(6);
45+
expect(GetTiles[0]).toContain('6/33/20.png')
46+
expect(GetTiles[1]).toContain('6/33/21.png')
47+
expect(GetTiles[2]).toContain('6/34/20.png')
48+
expect(GetTiles[3]).toContain('6/34/21.png')
49+
expect(GetTiles[4]).toContain('6/33/22.png')
50+
expect(GetTiles[5]).toContain('6/34/22.png')
51+
await page.unroute('https://tile.openstreetmap.org/*/*/*.png')
52+
});
53+
54+
test('Axis Orientation NEU for EPSG:3844', async ({ page }) => {
55+
const url = '/index.php/view/map/?repository=testsrepository&project=axis_orientation_neu_3844';
56+
await page.goto(url, { waitUntil: 'networkidle' });
57+
58+
const getMapPromise = page.waitForRequest(/GetMap/);
59+
await page.getByLabel('județ').check();
60+
const getMapRequest = await getMapPromise;
61+
const getMapUrl = getMapRequest.url();
62+
expect(getMapUrl).toContain('SERVICE=WMS');
63+
expect(getMapUrl).toContain('VERSION=1.3.0');
64+
expect(getMapUrl).toContain('REQUEST=GetMap');
65+
expect(getMapUrl).toContain('FORMAT=image%2Fpng');
66+
expect(getMapUrl).toContain('TRANSPARENT=true');
67+
expect(getMapUrl).toContain('LAYERS=judet');
68+
expect(getMapUrl).toContain('CRS=EPSG%3A3844');
69+
expect(getMapUrl).toContain('STYLES=default');
70+
expect(getMapUrl).toContain('WIDTH=958');
71+
expect(getMapUrl).toContain('HEIGHT=633');
72+
expect(getMapUrl).toMatch(/BBOX=72126.00\d+%2C-122200.57\d+%2C909533.92\d+%2C1145156.12\d+/);
73+
74+
const getMapResponse = await getMapRequest.response();
75+
expect(getMapResponse).not.toBeNull();
76+
expect(getMapResponse?.ok()).toBe(true);
77+
expect(await getMapResponse?.headerValue('Content-Type')).toBe('image/png');
78+
// image size greater than transparent
79+
const contentLength = await getMapResponse?.headerValue('Content-Length');
80+
expect(parseInt(contentLength ? contentLength : '0')).toBeGreaterThan(5552);
81+
// image size lesser than disorder axis
82+
expect(parseInt(contentLength ? contentLength : '0')).toBeLessThan(240115);
83+
84+
// Catch GetTile request;
85+
let GetTiles = [];
86+
await page.route('https://tile.openstreetmap.org/*/*/*.png', (route) => {
87+
const request = route.request();
88+
GetTiles.push(request.url());
89+
}, {times: 6});
90+
await page.locator('#switcher-baselayer').getByRole('combobox').selectOption('OpenStreetMap');
91+
await page.waitForTimeout(1000);
92+
expect(GetTiles).toHaveLength(6);
93+
expect(GetTiles[0]).toContain('6/35/22.png')
94+
expect(GetTiles[1]).toContain('6/35/23.png')
95+
expect(GetTiles[2]).toContain('6/36/22.png')
96+
expect(GetTiles[3]).toContain('6/36/23.png')
97+
expect(GetTiles[4]).toContain('6/37/22.png')
98+
expect(GetTiles[5]).toContain('6/37/23.png')
99+
await page.unroute('https://tile.openstreetmap.org/*/*/*.png')
100+
});
101+
});
Binary file not shown.

0 commit comments

Comments
 (0)