leaflet-rotate 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/leaflet-rotate-src.js +105 -5
- package/dist/leaflet-rotate-src.js.map +2 -2
- package/dist/leaflet-rotate.js +1 -1
- package/dist/leaflet-rotate.js.map +3 -3
- package/package.json +1 -1
- package/src/map/Map.js +122 -5
- package/test/features/rotation.feature +5 -0
- package/test/index.html +242 -23
- package/test/steps/{bearing.steps.js → rotation.steps.js} +119 -15
|
@@ -167,10 +167,8 @@ When('I drag the pitched map', async function () {
|
|
|
167
167
|
rect = container.getBoundingClientRect(),
|
|
168
168
|
start = [rect.left + rect.width / 2, rect.top + rect.height / 2];
|
|
169
169
|
const vectorScreenPoint = () => {
|
|
170
|
-
const bounds = element.
|
|
171
|
-
|
|
172
|
-
.matrixTransform(element.getScreenCTM());
|
|
173
|
-
return [point.x, point.y];
|
|
170
|
+
const bounds = element.getBoundingClientRect();
|
|
171
|
+
return [bounds.left + bounds.width / 2, bounds.top + bounds.height / 2];
|
|
174
172
|
};
|
|
175
173
|
const before = vectorScreenPoint(),
|
|
176
174
|
pinBoundsBefore = pinElement.getBoundingClientRect(),
|
|
@@ -183,18 +181,42 @@ When('I drag the pitched map', async function () {
|
|
|
183
181
|
await this.page.mouse.move(dragStart[0] + 70, dragStart[1] + 35, { steps: 4 });
|
|
184
182
|
await this.page.mouse.up();
|
|
185
183
|
this.pitchedDrag = await this.page.evaluate(() => {
|
|
186
|
-
const { pinElement, vectorScreenPoint
|
|
184
|
+
const { pinElement, vectorScreenPoint } = window.__pitchPanTest,
|
|
187
185
|
after = vectorScreenPoint(),
|
|
188
186
|
pinBoundsAfter = pinElement.getBoundingClientRect(),
|
|
189
|
-
pinAfter = [pinBoundsAfter.left + pinBoundsAfter.width / 2, pinBoundsAfter.top + pinBoundsAfter.height / 2]
|
|
190
|
-
|
|
187
|
+
pinAfter = [pinBoundsAfter.left + pinBoundsAfter.width / 2, pinBoundsAfter.top + pinBoundsAfter.height / 2],
|
|
188
|
+
container = map.getContainer().getBoundingClientRect(),
|
|
189
|
+
expected = map.latLngToContainerPoint(new L.LatLng(55, 10));
|
|
191
190
|
return {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
vector: [after[0] - container.left, after[1] - container.top],
|
|
192
|
+
expected: [expected.x, expected.y],
|
|
193
|
+
vectorError: Math.hypot(after[0] - container.left - expected.x, after[1] - container.top - expected.y),
|
|
194
|
+
pinError: Math.hypot(pinAfter[0] - container.left - expected.x, pinAfter[1] - container.top - expected.y),
|
|
196
195
|
};
|
|
197
196
|
});
|
|
197
|
+
this.repeatedPitchPanErrors = [];
|
|
198
|
+
for (let i = 0; i < 2; i++) {
|
|
199
|
+
const bounds = await this.page.locator('#map').boundingBox(),
|
|
200
|
+
center = { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
201
|
+
await this.page.mouse.move(center.x, center.y);
|
|
202
|
+
await this.page.mouse.down();
|
|
203
|
+
await this.page.mouse.move(center.x + 40, center.y + 20, { steps: 3 });
|
|
204
|
+
await this.page.mouse.up();
|
|
205
|
+
this.repeatedPitchPanErrors.push(await this.page.evaluate(() => {
|
|
206
|
+
const { element, pinElement } = window.__pitchPanTest,
|
|
207
|
+
bounds = element.getBoundingClientRect(),
|
|
208
|
+
vector = [bounds.left + bounds.width / 2, bounds.top + bounds.height / 2],
|
|
209
|
+
pinBounds = pinElement.getBoundingClientRect(),
|
|
210
|
+
pin = [pinBounds.left + pinBounds.width / 2, pinBounds.top + pinBounds.height / 2],
|
|
211
|
+
container = map.getContainer().getBoundingClientRect(),
|
|
212
|
+
expected = map.latLngToContainerPoint(new L.LatLng(55, 10));
|
|
213
|
+
return [
|
|
214
|
+
Math.hypot(vector[0] - container.left - expected.x, vector[1] - container.top - expected.y),
|
|
215
|
+
Math.hypot(pin[0] - container.left - expected.x, pin[1] - container.top - expected.y),
|
|
216
|
+
];
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
await this.page.evaluate(() => { delete window.__pitchPanTest; });
|
|
198
220
|
});
|
|
199
221
|
|
|
200
222
|
Then('vector layers stay aligned with their map coordinates', function () {
|
|
@@ -215,10 +237,92 @@ Then('vector layers stay aligned with their map coordinates', function () {
|
|
|
215
237
|
}
|
|
216
238
|
return;
|
|
217
239
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
assert.ok(
|
|
240
|
+
assert.ok(this.pitchedDrag.vectorError < 2, JSON.stringify(this.pitchedDrag));
|
|
241
|
+
assert.ok(this.pitchedDrag.pinError < 2, JSON.stringify(this.pitchedDrag));
|
|
242
|
+
for (const errors of this.repeatedPitchPanErrors) {
|
|
243
|
+
assert.ok(errors[0] < 2 && errors[1] < 2, JSON.stringify(this.repeatedPitchPanErrors));
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
When('I pan a highly pitched map in several directions', { timeout: 30000 }, async function () {
|
|
248
|
+
this.pitchPanErrors = [];
|
|
249
|
+
this.page.on('pageerror', error => this.pitchPanErrors.push(error.message));
|
|
250
|
+
await this.page.evaluate(() => {
|
|
251
|
+
map.setView([50, 5], 5, { animate: false });
|
|
252
|
+
map.setBearing(45);
|
|
253
|
+
map.setPitch(70);
|
|
254
|
+
map.options.inertia = false;
|
|
255
|
+
const size = map.getSize(),
|
|
256
|
+
locations = [[0.35, 0.35], [0.65, 0.35], [0.35, 0.65], [0.65, 0.65]]
|
|
257
|
+
.map(([x, y]) => map.containerPointToLatLng([size.x * x, size.y * y])),
|
|
258
|
+
marker = new L.Marker(locations[0]).addTo(map),
|
|
259
|
+
vector = new L.CircleMarker(locations[0], { radius: 4 }).addTo(map),
|
|
260
|
+
bounds = element => {
|
|
261
|
+
const rect = element.getBoundingClientRect();
|
|
262
|
+
return [rect.x, rect.y, rect.width, rect.height];
|
|
263
|
+
};
|
|
264
|
+
window.__pitchRelease = {
|
|
265
|
+
locations,
|
|
266
|
+
resets: 0,
|
|
267
|
+
measure: () => ({
|
|
268
|
+
points: locations.map(ll => map.latLngToContainerPoint(ll)),
|
|
269
|
+
marker: bounds(marker.getElement()),
|
|
270
|
+
vector: bounds(vector.getElement()),
|
|
271
|
+
}),
|
|
272
|
+
};
|
|
273
|
+
map.on('viewreset', () => window.__pitchRelease.resets++);
|
|
274
|
+
});
|
|
275
|
+
this.pitchReleases = [];
|
|
276
|
+
for (const [dx, dy] of [[0, -60], [-60, 0], [30, 50], [30, 10]]) {
|
|
277
|
+
const bounds = await this.page.locator('#map').boundingBox(),
|
|
278
|
+
x = bounds.x + bounds.width / 2,
|
|
279
|
+
y = bounds.y + bounds.height / 2;
|
|
280
|
+
await this.page.mouse.move(x, y);
|
|
281
|
+
await this.page.mouse.down();
|
|
282
|
+
await this.page.mouse.move(x + dx, y + dy, { steps: 10 });
|
|
283
|
+
const before = await this.page.evaluate(() => window.__pitchRelease.measure());
|
|
284
|
+
await this.page.mouse.up();
|
|
285
|
+
await this.page.waitForTimeout(50);
|
|
286
|
+
this.pitchReleases.push(await this.page.evaluate(before => {
|
|
287
|
+
const state = window.__pitchRelease,
|
|
288
|
+
after = state.measure(),
|
|
289
|
+
center = map.project(map.getCenter()),
|
|
290
|
+
size = map.getSize(),
|
|
291
|
+
bearing = map.getBearing() * Math.PI / 180,
|
|
292
|
+
pitch = map.getPitch() * Math.PI / 180,
|
|
293
|
+
perspective = size.y * 1.5,
|
|
294
|
+
projectionErrors = state.locations.map(ll => {
|
|
295
|
+
const point = map.project(ll).subtract(center),
|
|
296
|
+
x = point.x * Math.cos(bearing) - point.y * Math.sin(bearing),
|
|
297
|
+
y = point.x * Math.sin(bearing) + point.y * Math.cos(bearing),
|
|
298
|
+
scale = perspective / (perspective - y * Math.sin(pitch)),
|
|
299
|
+
actual = map.latLngToContainerPoint(ll);
|
|
300
|
+
return Math.hypot(actual.x - size.x / 2 - x * scale, actual.y - size.y / 2 - y * Math.cos(pitch) * scale);
|
|
301
|
+
});
|
|
302
|
+
return { before, after, resets: state.resets, projectionErrors };
|
|
303
|
+
}, before));
|
|
304
|
+
}
|
|
305
|
+
this.pitchPanToError = await this.page.evaluate(() => {
|
|
306
|
+
const center = map.getCenter(),
|
|
307
|
+
target = new L.LatLng(center.lat + 0.1, center.lng + 0.1);
|
|
308
|
+
map.panTo(target, { animate: false });
|
|
309
|
+
return map.latLngToContainerPoint(target).distanceTo(map.getSize().divideBy(2));
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
Then('releasing the map does not jump or reset its layers', function () {
|
|
314
|
+
assert.deepEqual(this.pitchPanErrors, [], 'Pitched dragging must not throw JavaScript errors');
|
|
315
|
+
assert.ok(this.pitchPanToError < 1.5, `panTo missed its target by ${this.pitchPanToError}px`);
|
|
316
|
+
for (const result of this.pitchReleases) {
|
|
317
|
+
assert.equal(result.resets, 0, 'Releasing a pan must not reset the view or reload its tiles');
|
|
318
|
+
for (let i = 0; i < result.before.points.length; i++) {
|
|
319
|
+
const before = result.before.points[i], after = result.after.points[i];
|
|
320
|
+
assert.ok(Math.hypot(before.x - after.x, before.y - after.y) < 0.1, JSON.stringify(result));
|
|
321
|
+
}
|
|
322
|
+
for (const layer of ['marker', 'vector']) {
|
|
323
|
+
assert.ok(result.before[layer].every((value, i) => Math.abs(value - result.after[layer][i]) < 0.1), JSON.stringify(result));
|
|
324
|
+
}
|
|
325
|
+
assert.ok(result.projectionErrors.every(error => error < 1.5), JSON.stringify(result));
|
|
222
326
|
}
|
|
223
327
|
});
|
|
224
328
|
|