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

Add bearing to markers #8836

Merged
merged 20 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
37 changes: 37 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Display a map</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='dist/mapbox-gl.js'></script>
<link href='dist/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>

<div id='map'></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiZGJ1cm5zaWkiLCJhIjoiY2p4Nm5uand6MDFraDNubm5vaThkdGdnbyJ9.TOwUFMiTxFwuAhChYmN0rw';
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/streets-v11', // stylesheet location
center: [-74.50, 40], // starting position [lng, lat]
zoom: 9, // starting zoom
pitch: 50
});

var marker = new mapboxgl.Marker({rotation: 45, rotationAlignment: "map", pitchAlignment: "map", anchor: "top"}).setLngLat([-74.50, 40]).addTo(map);
new mapboxgl.Marker().setLngLat([-74.60, 40]).addTo(map);

//window.setInterval(function(){
// marker.setBearing(marker.getBearing() + 5);
//}, 50);

</script>

</body>
</html>
87 changes: 85 additions & 2 deletions src/ui/marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ type Options = {
offset?: PointLike,
anchor?: Anchor,
color?: string,
draggable?: boolean
draggable?: boolean,
rotation?: number,
rotationAlignment?: string,
pitchAlignment?: string
};

/**
Expand All @@ -31,6 +34,9 @@ type Options = {
* @param {PointLike} [options.offset] The offset in pixels as a {@link PointLike} object to apply relative to the element's center. Negatives indicate left and up.
* @param {string} [options.color='#3FB1CE'] The color to use for the default marker if options.element is not provided. The default is light blue.
* @param {boolean} [options.draggable=false] A boolean indicating whether or not a marker is able to be dragged to a new position on the map.
* @param {number} [options.rotation=0] The rotation angle of the marker (clockwise, in degrees), relative to its respective {@link Marker#rotationAlignment} setting.
Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I can tell, nothing in this PR restricts rotation to the clockwise direction (which I think is good!). If you set rotation = -45, then the marker will rotate 45 degrees counterclockwise, right? So we should spell that out here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe just enough to replace "clockwise, in degrees" with "a positive rotation angle is clockwise, units in degrees"?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe The rotation angle of the marker in degrees, relative to its respective {@link Marker#rotationAlignment} setting. A positive value will rotate the marker clockwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the parameter description.

* @param {string} [options.pitchAlignment='auto'] `map` aligns the `Marker`'s pitch relative to the map, `viewport` aligns the `Marker`'s pitch relative to the viewport.
* @param {string} [options.rotationAlignment='auto'] `map` aligns the `Marker`'s rotation relative to the map, `viewport` aligns the `Marker`'s rotation relative to the viewport.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should explicitly note that by default, pitchAlignment and rotationAlignment are set to auto which in the case of rotationAlignment is equivalent to viewport. But by analogy with icon-rotation-alignment and icon-pitch-alignment, we can see that if icon-pitch-alignment is set to auto, it matches whatever icon-rotation-alignment is set to. I think it would be good to match that behavior with these options.

Copy link
Contributor

Choose a reason for hiding this comment

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

I also think that we could more closely align the wording of the descriptions here with what we have already for the symbol style properties. So for pitchAlignment, something like "map aligns the Marker to the plane of the map. viewport aligns the Marker to the plane of the viewport`." We also should use proper punctuation in the comments.

cc @chloekraw for thoughts on wording (you may want to review the wording for the function descriptions starting on line 515 as well)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used your wording for pitchAlignment, but there are extra properties that make the icon-rotation-alignmentdescription as easy to drop in. Changed that to:

"map aligns the Marker's rotation relative to the map, maintaining a bearing as the map rotates. viewport aligns the Marker's rotation relative to the viewport, agnostic to map rotations. auto is equivalent to viewport."

Thoughts?

* @example
* var marker = new mapboxgl.Marker()
* .setLngLat([30.5, 50.5])
Expand All @@ -51,6 +57,9 @@ export default class Marker extends Evented {
_draggable: boolean;
_state: 'inactive' | 'pending' | 'active'; // used for handling drag events
_positionDelta: ?number;
_rotation: number;
_pitchAlignment: string;
_rotationAlignment: string;

constructor(options?: Options, legacyOptions?: Options) {
super();
Expand All @@ -72,6 +81,9 @@ export default class Marker extends Evented {
this._color = options && options.color || '#3FB1CE';
this._draggable = options && options.draggable || false;
this._state = 'inactive';
this._rotation = options && options.rotation || 0;
this._pitchAlignment = options && options.pitchAlignment || 'auto';
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, I think if options.pitchAlignment is auto, this._pitchAlignment should match whatever options.rotationAlignment is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Set to options && options.pitchAlignment || this._rotationAlignment, putting it after this._rotationAlignment is set.

this._rotationAlignment = options && options.rotationAlignment || 'auto';

if (!options || !options.element) {
this._defaultMarker = true;
Expand Down Expand Up @@ -344,14 +356,28 @@ export default class Marker extends Evented {

this._pos = this._map.project(this._lngLat)._add(this._offset);

let rotation = "";
if (this._rotationAlignment === "viewport" || this._rotationAlignment === "auto") {
rotation = `rotateZ(${this._rotation}deg)`;
} else if (this._rotationAlignment === "map") {
rotation = `rotateZ(${this._rotation - this._map.getBearing()}deg)`;
}

let pitch = "";
if (this._pitchAlignment === "viewport" || this._pitchAlignment === "auto") {
pitch = "rotateX(0deg)";
} else if (this._pitchAlignment === "map") {
pitch = `rotateX(${this._map.getPitch()}deg)`;
}

// because rounding the coordinates at every `move` event causes stuttered zooming
// we only round them when _update is called with `moveend` or when its called with
// no arguments (when the Marker is initialized or Marker#setLngLat is invoked).
if (!e || e.type === "moveend") {
this._pos = this._pos.round();
}

DOM.setTransform(this._element, `${anchorTranslate[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px)`);
DOM.setTransform(this._element, `${anchorTranslate[this._anchor]} translate(${this._pos.x}px, ${this._pos.y}px) ${pitch} ${rotation}`);
}

/**
Expand Down Expand Up @@ -484,4 +510,61 @@ export default class Marker extends Evented {
isDraggable() {
return this._draggable;
}

/**
* Sets the `rotation` property for pointing the marker in a particular direction
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the wording on this comment isn't quite right. If the marker is set relative to the viewport, then it's not really correct to say it's pointing in a particular "direction". There's also no indication in the description of the method or the rotation parameter that rotation is in degrees or what positive and negative degrees would do (clockwise vs counter-clockwise).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed this should be changed, but it goes back to if we should repeat the documentation for the property, or omit duplicating it and expect people to refer back to the rotation property documentation.

I think less duplication is better as you don't need to re-read the same documentation in multiple places and ensure it's consistent.

* @param {number} [options.rotation=0] The rotation angle of the marker (clockwise, in degrees), relative to its respective {@link Marker#rotationAlignment} setting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to "Sets the rotationAlignment of the marker."

* @param {number} [rotation=0] Sets the direction the marker is pointing
* @returns {Marker} `this`
*/
setRotation(rotation: number) {
this._rotation = rotation || 0;
this._update();
return this;
}

/**
* Returns the current rotation angle
Copy link
Contributor

Choose a reason for hiding this comment

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

in degrees

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

* @returns {number}
*/
getRotation() {
return this._rotation;
}

/**
* Sets the `rotateAlignment` property for pointing the marker in a particular direction
Copy link
Contributor

Choose a reason for hiding this comment

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

reference to direction here as well. I'm not sure what the best way to say this is, but I think this isn't quite right. @andrewharvey thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would just use "Sets the rotationAlignment of the marker." Further documentation of exactly what rotationAlignment is is further up in the options and I don't think needs to be duplicated here? At least this is what other options like draggable/setDraggable do. Open to other opinions though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I like that approach and I totally missed that this needs to be rotationAlignment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Used that wording for setPitchAlignment and setRotationAlignment, and fixed the rotateAlignment typo.

* @param {string} [alignment='auto'] Sets the reference for marker rotations
* @returns {Marker} `this`
*/
setRotationAlignment(alignment: string) {
this._rotationAlignment = alignment || 'auto';
this._update();
return this;
}

/**
* Returns the current rotation alignment
* @returns {string}
*/
getRotationAlignment() {
return this._rotationAlignment;
}

/**
* Sets the `pitchAlignment` property for aligning the pitch of a marker with the viewport or the map
* @param {string} [alignment='auto'] Sets the pitch alignment property
* @returns {Marker} `this`
*/
setPitchAlignment(alignment: string) {
this._pitchAlignment = alignment || 'auto';
Copy link
Contributor

@ryanhamley ryanhamley Oct 22, 2019

Choose a reason for hiding this comment

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

This would also need to change to match rotationAlignment when alignment is auto

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to alignment || this._rotationAlignment

this._update();
return this;
}

/**
* Returns the current pitch alignment
* @returns {string}
*/
getPitchAlignment() {
return this._pitchAlignment;
}
}
66 changes: 66 additions & 0 deletions test/unit/ui/marker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,69 @@ test('Marker with draggable:true does not error if removed on mousedown', (t) =>
t.ok(map.fire('mouseup'));
t.end();
});

test('Marker can set rotationAlignment and pitchAlignment', (t) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably add a test that ensures setting rotationAlignment === 'map' and pitchAlignment === 'auto' results in pitchAlignment === 'map'. Both through the options object and by calling set{Rotation/Pitch}Alignment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a couple tests. One is for the option on initialization, the other is for the getter/setters.

const map = createMap(t);
const marker = new Marker({rotationAlignment: 'map', pitchAlignment: 'map'})
.setLngLat([0, 0])
.addTo(map);

t.equal(marker.getRotationAlignment(), 'map');
t.equal(marker.getPitchAlignment(), 'map');

map.remove();
t.end();
});

test('Marker can set and update rotation', (t) => {
const map = createMap(t);
const marker = new Marker({rotation: 45})
.setLngLat([0, 0])
.addTo(map);

t.equal(marker.getRotation(), 45);

marker.setRotation(90);
t.equal(marker.getRotation(), 90);

map.remove();
t.end();
});

test('Marker transforms rotation with the map', (t) => {
const map = createMap(t);
const marker = new Marker({rotationAlignment: 'map'})
.setLngLat([0, 0])
.addTo(map);

const rotationRegex = /rotateZ\(-?([0-9]+)deg\)/;
const initialRotation = marker.getElement().style.transform.match(rotationRegex)[1];

map.setBearing(map.getBearing() + 180);

const finalRotation = marker.getElement().style.transform.match(rotationRegex)[1];
t.notEqual(initialRotation, finalRotation);

map.remove();
t.end();
});

test('Marker transforms pitch with the map', (t) => {
const map = createMap(t);
const marker = new Marker({pitchAlignment: 'map'})
.setLngLat([0, 0])
.addTo(map);

map.setPitch(0);

const rotationRegex = /rotateX\(-?([0-9]+)deg\)/;
const initialPitch = marker.getElement().style.transform.match(rotationRegex)[1];

map.setPitch(45);

const finalPitch = marker.getElement().style.transform.match(rotationRegex)[1];
t.notEqual(initialPitch, finalPitch);

map.remove();
t.end();
});