-
Notifications
You must be signed in to change notification settings - Fork 640
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
78 changed files
with
17,337 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
## Map | ||
|
||
Map fork from [maplibre-gl-js](maplibre-gl-js), keep event, responds user interaction and updates the internal state of the map (current viewport, camera angle, etc.) | ||
|
||
```mermaid | ||
sequenceDiagram | ||
actor user | ||
participant DOM | ||
participant handler_manager | ||
participant handler | ||
participant camera | ||
participant transform | ||
participant map | ||
user->>camera: map#setCenter, map#panTo | ||
camera->>transform: update | ||
camera->>map: fire move event | ||
map->>map: _render() | ||
user->>DOM: resize, pan,<br>click, scroll,<br>... | ||
DOM->>handler_manager: DOM events | ||
handler_manager->>handler: forward event | ||
handler-->>handler_manager: HandlerResult | ||
handler_manager->>transform: update | ||
handler_manager->>map: fire move event | ||
map->>map: _render() | ||
``` | ||
|
||
- [Transform](../src/geo/transform.ts) holds the current viewport details (pitch, zoom, bearing, bounds, etc.). Two places in the code update transform directly: | ||
- [Camera](../src/ui/camera.ts) (parent class of [Map](../src/ui/map)) in response to explicit calls to [Camera#panTo](../src/ui/camera.ts#L207), [Camera#setCenter](../src/ui/camera.ts#L169) | ||
- [HandlerManager](../src/ui/handler_manager.ts) in response to DOM events. It forwards those events to interaction processors that live in [src/ui/handler](../src/ui/handler), which accumulate a merged [HandlerResult](../src/ui/handler_manager.ts#L64) that kick off a render frame loop, decreasing the inertia and nudging map.transform by that amount on each frame from [HandlerManager#\_updateMapTransform()](../src/ui/handler_manager.ts#L413). That loop continues in the inertia decreases to 0. | ||
- Both camera and handler_manager are responsible for firing `move`, `zoom`, `movestart`, `moveend`, ... events on the map after they update transform. Each of these events (along with style changes and data load events) triggers a call to [Map#\_render()](../src/ui/map.ts#L2480) which renders a single frame of the map. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { EdgeInsets } from '../geo/edge_insets'; | ||
|
||
describe('EdgeInsets', () => { | ||
describe('#constructor', () => { | ||
test('creates an object with default values', () => { | ||
expect(new EdgeInsets() instanceof EdgeInsets).toBeTruthy(); | ||
}); | ||
|
||
test('invalid initialization', () => { | ||
expect(() => { | ||
new EdgeInsets(NaN, 10); | ||
}).toThrow('Invalid value for edge-insets, top, bottom, left and right must all be numbers'); | ||
|
||
expect(() => { | ||
new EdgeInsets(-10, 10, 20, 10); | ||
}).toThrow('Invalid value for edge-insets, top, bottom, left and right must all be numbers'); | ||
}); | ||
|
||
test('valid initialization', () => { | ||
const top = 10; | ||
const bottom = 15; | ||
const left = 26; | ||
const right = 19; | ||
|
||
const inset = new EdgeInsets(top, bottom, left, right); | ||
expect(inset.top).toBe(top); | ||
expect(inset.bottom).toBe(bottom); | ||
expect(inset.left).toBe(left); | ||
expect(inset.right).toBe(right); | ||
}); | ||
}); | ||
|
||
describe('#getCenter', () => { | ||
test('valid input', () => { | ||
const inset = new EdgeInsets(10, 15, 50, 10); | ||
const center = inset.getCenter(600, 400); | ||
expect(center.x).toBe(320); | ||
expect(center.y).toBe(197.5); | ||
}); | ||
|
||
test('center clamping', () => { | ||
const inset = new EdgeInsets(300, 200, 500, 200); | ||
const center = inset.getCenter(600, 400); | ||
|
||
// Midpoint of the overlap when padding overlaps | ||
expect(center.x).toBe(450); | ||
expect(center.y).toBe(250); | ||
}); | ||
}); | ||
|
||
describe('#interpolate', () => { | ||
test('it works', () => { | ||
const inset1 = new EdgeInsets(10, 15, 50, 10); | ||
const inset2 = new EdgeInsets(20, 30, 100, 10); | ||
const inset3 = inset1.interpolate(inset1, inset2, 0.5); | ||
|
||
// inset1 is mutated in-place | ||
expect(inset3).toBe(inset1); | ||
|
||
expect(inset3.top).toBe(15); | ||
expect(inset3.bottom).toBe(22.5); | ||
expect(inset3.left).toBe(75); | ||
expect(inset3.right).toBe(10); | ||
}); | ||
}); | ||
|
||
test('#equals', () => { | ||
const inset1 = new EdgeInsets(10, 15, 50, 10); | ||
const inset2 = new EdgeInsets(10, 15, 50, 10); | ||
const inset3 = new EdgeInsets(10, 15, 50, 11); | ||
expect(inset1.equals(inset2)).toBeTruthy(); | ||
expect(inset2.equals(inset3)).toBeFalsy(); | ||
}); | ||
|
||
test('#clone', () => { | ||
const inset1 = new EdgeInsets(10, 15, 50, 10); | ||
const inset2 = inset1.clone(); | ||
expect(inset2 === inset1).toBeFalsy(); | ||
expect(inset1.equals(inset2)).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import Point from '@mapbox/point-geometry'; | ||
import { clamp, interpolates } from '../util/util'; | ||
|
||
/** | ||
* An `EdgeInset` object represents screen space padding applied to the edges of the viewport. | ||
* This shifts the apprent center or the vanishing point of the map. This is useful for adding floating UI elements | ||
* on top of the map and having the vanishing point shift as UI elements resize. | ||
* | ||
* @group Geography and Geometry | ||
*/ | ||
export class EdgeInsets { | ||
/** | ||
* @defaultValue 0 | ||
*/ | ||
top: number; | ||
/** | ||
* @defaultValue 0 | ||
*/ | ||
bottom: number; | ||
/** | ||
* @defaultValue 0 | ||
*/ | ||
left: number; | ||
/** | ||
* @defaultValue 0 | ||
*/ | ||
right: number; | ||
|
||
constructor(top: number = 0, bottom: number = 0, left: number = 0, right: number = 0) { | ||
if ( | ||
isNaN(top) || | ||
top < 0 || | ||
isNaN(bottom) || | ||
bottom < 0 || | ||
isNaN(left) || | ||
left < 0 || | ||
isNaN(right) || | ||
right < 0 | ||
) { | ||
throw new Error( | ||
'Invalid value for edge-insets, top, bottom, left and right must all be numbers', | ||
); | ||
} | ||
|
||
this.top = top; | ||
this.bottom = bottom; | ||
this.left = left; | ||
this.right = right; | ||
} | ||
|
||
/** | ||
* Interpolates the inset in-place. | ||
* This maintains the current inset value for any inset not present in `target`. | ||
* @param start - interpolation start | ||
* @param target - interpolation target | ||
* @param t - interpolation step/weight | ||
* @returns the insets | ||
*/ | ||
interpolate(start: PaddingOptions | EdgeInsets, target: PaddingOptions, t: number): EdgeInsets { | ||
if (target.top != null && start.top != null) | ||
this.top = interpolates.number(start.top, target.top, t); | ||
if (target.bottom != null && start.bottom != null) | ||
this.bottom = interpolates.number(start.bottom, target.bottom, t); | ||
if (target.left != null && start.left != null) | ||
this.left = interpolates.number(start.left, target.left, t); | ||
if (target.right != null && start.right != null) | ||
this.right = interpolates.number(start.right, target.right, t); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Utility method that computes the new apprent center or vanishing point after applying insets. | ||
* This is in pixels and with the top left being (0.0) and +y being downwards. | ||
* | ||
* @param width - the width | ||
* @param height - the height | ||
* @returns the point | ||
*/ | ||
getCenter(width: number, height: number): Point { | ||
// Clamp insets so they never overflow width/height and always calculate a valid center | ||
const x = clamp((this.left + width - this.right) / 2, 0, width); | ||
const y = clamp((this.top + height - this.bottom) / 2, 0, height); | ||
|
||
return new Point(x, y); | ||
} | ||
|
||
equals(other: PaddingOptions): boolean { | ||
return ( | ||
this.top === other.top && | ||
this.bottom === other.bottom && | ||
this.left === other.left && | ||
this.right === other.right | ||
); | ||
} | ||
|
||
clone(): EdgeInsets { | ||
return new EdgeInsets(this.top, this.bottom, this.left, this.right); | ||
} | ||
|
||
/** | ||
* Returns the current state as json, useful when you want to have a | ||
* read-only representation of the inset. | ||
* | ||
* @returns state as json | ||
*/ | ||
toJSON(): PaddingOptions { | ||
return { | ||
top: this.top, | ||
bottom: this.bottom, | ||
left: this.left, | ||
right: this.right, | ||
}; | ||
} | ||
} | ||
|
||
/** | ||
* Options for setting padding on calls to methods such as {@link Map#fitBounds}, {@link Map#fitScreenCoordinates}, and {@link Map#setPadding}. Adjust these options to set the amount of padding in pixels added to the edges of the canvas. Set a uniform padding on all edges or individual values for each edge. All properties of this object must be | ||
* non-negative integers. | ||
* | ||
* @group Geography and Geometry | ||
* | ||
* @example | ||
* ```ts | ||
* let bbox = [[-79, 43], [-73, 45]]; | ||
* map.fitBounds(bbox, { | ||
* padding: {top: 10, bottom:25, left: 15, right: 5} | ||
* }); | ||
* ``` | ||
* | ||
* @example | ||
* ```ts | ||
* let bbox = [[-79, 43], [-73, 45]]; | ||
* map.fitBounds(bbox, { | ||
* padding: 20 | ||
* }); | ||
* ``` | ||
* @see [Fit to the bounds of a LineString](https://maplibre.org/maplibre-gl-js/docs/examples/zoomto-linestring/) | ||
* @see [Fit a map to a bounding box](https://maplibre.org/maplibre-gl-js/docs/examples/fitbounds/) | ||
*/ | ||
export type PaddingOptions = { | ||
/** | ||
* Padding in pixels from the top of the map canvas. | ||
*/ | ||
top: number; | ||
/** | ||
* Padding in pixels from the bottom of the map canvas. | ||
*/ | ||
bottom: number; | ||
/** | ||
* Padding in pixels from the left of the map canvas. | ||
*/ | ||
right: number; | ||
/** | ||
* Padding in pixels from the right of the map canvas. | ||
*/ | ||
left: number; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { LngLat } from '../geo/lng_lat'; | ||
|
||
describe('LngLat', () => { | ||
test('#constructor', () => { | ||
expect(new LngLat(0, 0) instanceof LngLat).toBeTruthy(); | ||
|
||
expect(() => { | ||
/*eslint no-new: 0*/ | ||
new LngLat(0, -91); | ||
}).toThrow('Invalid LngLat latitude value: must be between -90 and 90'); | ||
|
||
expect(() => { | ||
/*eslint no-new: 0*/ | ||
new LngLat(0, 91); | ||
}).toThrow('Invalid LngLat latitude value: must be between -90 and 90'); | ||
}); | ||
|
||
test('#convert', () => { | ||
expect(LngLat.convert([0, 10]) instanceof LngLat).toBeTruthy(); | ||
expect(LngLat.convert({ lng: 0, lat: 10 }) instanceof LngLat).toBeTruthy(); | ||
expect(LngLat.convert({ lng: 0, lat: 0 }) instanceof LngLat).toBeTruthy(); | ||
expect(LngLat.convert({ lon: 0, lat: 10 }) instanceof LngLat).toBeTruthy(); | ||
expect(LngLat.convert({ lon: 0, lat: 0 }) instanceof LngLat).toBeTruthy(); | ||
expect(LngLat.convert(new LngLat(0, 0)) instanceof LngLat).toBeTruthy(); | ||
}); | ||
|
||
test('#wrap', () => { | ||
expect(new LngLat(0, 0).wrap()).toEqual({ lng: 0, lat: 0 }); | ||
expect(new LngLat(10, 20).wrap()).toEqual({ lng: 10, lat: 20 }); | ||
expect(new LngLat(360, 0).wrap()).toEqual({ lng: 0, lat: 0 }); | ||
expect(new LngLat(190, 0).wrap()).toEqual({ lng: -170, lat: 0 }); | ||
}); | ||
|
||
test('#toArray', () => { | ||
expect(new LngLat(10, 20).toArray()).toEqual([10, 20]); | ||
}); | ||
|
||
test('#toString', () => { | ||
expect(new LngLat(10, 20).toString()).toBe('LngLat(10, 20)'); | ||
}); | ||
|
||
test('#distanceTo', () => { | ||
const newYork = new LngLat(-74.006, 40.7128); | ||
const losAngeles = new LngLat(-118.2437, 34.0522); | ||
const d = newYork.distanceTo(losAngeles); // 3935751.690893987, "true distance" is 3966km | ||
expect(d > 3935750).toBeTruthy(); | ||
expect(d < 3935752).toBeTruthy(); | ||
}); | ||
|
||
test('#distanceTo to pole', () => { | ||
const newYork = new LngLat(-74.006, 40.7128); | ||
const northPole = new LngLat(-135, 90); | ||
const d = newYork.distanceTo(northPole); // 5480494.158486183 , "true distance" is 5499km | ||
expect(d > 5480493).toBeTruthy(); | ||
expect(d < 5480495).toBeTruthy(); | ||
}); | ||
|
||
test('#distanceTo to Null Island', () => { | ||
const newYork = new LngLat(-74.006, 40.7128); | ||
const nullIsland = new LngLat(0, 0); | ||
const d = newYork.distanceTo(nullIsland); // 8667080.125666846 , "true distance" is 8661km | ||
expect(d > 8667079).toBeTruthy(); | ||
expect(d < 8667081).toBeTruthy(); | ||
}); | ||
}); |
Oops, something went wrong.