Don't zoom after new position added, center when last position is not visible (fixes #161)
This commit is contained in:
parent
1180a9fba8
commit
f641b68e14
@ -32,9 +32,11 @@ import Vector from 'ol/source/Vector';
|
|||||||
import VectorLayer from 'ol/layer/Vector';
|
import VectorLayer from 'ol/layer/Vector';
|
||||||
import View from 'ol/View';
|
import View from 'ol/View';
|
||||||
import XYZ from 'ol/source/XYZ';
|
import XYZ from 'ol/source/XYZ';
|
||||||
|
import { containsCoordinate } from 'ol/extent.js';
|
||||||
|
|
||||||
export { Feature, Map, Overlay, View };
|
export { Feature, Map, Overlay, View };
|
||||||
export const control = { Control, Rotate, ScaleLine, Zoom, ZoomToExtent };
|
export const control = { Control, Rotate, ScaleLine, Zoom, ZoomToExtent };
|
||||||
|
export const extent = { containsCoordinate };
|
||||||
export const geom = { LineString, Point };
|
export const geom = { LineString, Point };
|
||||||
export const layer = { TileLayer, VectorLayer };
|
export const layer = { TileLayer, VectorLayer };
|
||||||
export const proj = { fromLonLat, toLonLat };
|
export const proj = { fromLonLat, toLonLat };
|
||||||
|
@ -364,6 +364,28 @@ export default class GoogleMapsApi {
|
|||||||
this.map.fitBounds(latLngBounds);
|
this.map.fitBounds(latLngBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is given position within viewport
|
||||||
|
* @param {number} id
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isPositionVisible(id) {
|
||||||
|
if (id >= this.markers.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.map.getBounds().contains(this.markers[id].getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Center to given position
|
||||||
|
* @param {number} id
|
||||||
|
*/
|
||||||
|
centerToPosition(id) {
|
||||||
|
if (id < this.markers.length) {
|
||||||
|
this.map.setCenter(this.markers[id].getPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update size
|
* Update size
|
||||||
*/
|
*/
|
||||||
|
@ -587,16 +587,36 @@ export default class OpenLayersApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fit to extent, respect max zoom
|
* Fit to extent, respect max zoom, add padding
|
||||||
* @param {Array.<number>} extent
|
* @param {Array.<number>} extent
|
||||||
* @return {Array.<number>}
|
* @return {Array.<number>}
|
||||||
*/
|
*/
|
||||||
fitToExtent(extent) {
|
fitToExtent(extent) {
|
||||||
this.map.getView().fit(extent, { padding: [ 40, 10, 10, 10 ], maxZoom: OpenLayersApi.ZOOM_MAX });
|
this.map.getView().fit(extent, { padding: OpenLayersApi.TRACK_PADDING, maxZoom: OpenLayersApi.ZOOM_MAX });
|
||||||
if (this.map.getView().getZoom() === OpenLayersApi.ZOOM_MAX) {
|
return this.map.getView().calculateExtent();
|
||||||
extent = this.map.getView().calculateExtent(this.map.getSize());
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is given position within viewport
|
||||||
|
* @param {number} id
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isPositionVisible(id) {
|
||||||
|
const mapExtent = this.map.getView().calculateExtent();
|
||||||
|
const marker = this.layerMarkers.getSource().getFeatureById(id).getGeometry();
|
||||||
|
return marker ? ol.extent.containsCoordinate(mapExtent, marker.getCoordinates()) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Center to given position
|
||||||
|
* @param {number} id
|
||||||
|
*/
|
||||||
|
centerToPosition(id) {
|
||||||
|
const marker = this.layerMarkers.getSource().getFeatureById(id).getGeometry();
|
||||||
|
if (marker) {
|
||||||
|
console.log(`Setting center to position ${id}`)
|
||||||
|
this.map.getView().setCenter(marker.getCoordinates());
|
||||||
}
|
}
|
||||||
return extent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -691,17 +711,17 @@ export default class OpenLayersApi {
|
|||||||
* @returns {number[]} Bounds [ lon_sw, lat_sw, lon_ne, lat_ne ]
|
* @returns {number[]} Bounds [ lon_sw, lat_sw, lon_ne, lat_ne ]
|
||||||
*/
|
*/
|
||||||
getBounds() {
|
getBounds() {
|
||||||
const extent = this.map.getView().calculateExtent(this.map.getSize());
|
const extent = this.map.getView().calculateExtent();
|
||||||
const sw = ol.proj.toLonLat([ extent[0], extent[1] ]);
|
const sw = ol.proj.toLonLat([ extent[0], extent[1] ]);
|
||||||
const ne = ol.proj.toLonLat([ extent[2], extent[3] ]);
|
const ne = ol.proj.toLonLat([ extent[2], extent[3] ]);
|
||||||
return [ sw[0], sw[1], ne[0], ne[1] ];
|
return [ sw[0], sw[1], ne[0], ne[1] ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom to track extent, respect max zoom
|
* Zoom to track extent, respect max zoom, add padding
|
||||||
*/
|
*/
|
||||||
zoomToExtent() {
|
zoomToExtent() {
|
||||||
this.map.getView().fit(this.layerMarkers.getSource().getExtent(), { maxZoom: OpenLayersApi.ZOOM_MAX });
|
this.fitToExtent(this.layerMarkers.getSource().getExtent());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -760,3 +780,5 @@ export default class OpenLayersApi {
|
|||||||
}
|
}
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
OpenLayersApi.ZOOM_MAX = 20;
|
OpenLayersApi.ZOOM_MAX = 20;
|
||||||
|
/** @type {number[]} */
|
||||||
|
OpenLayersApi.TRACK_PADDING = [ 40, 10, 10, 10 ];
|
||||||
|
@ -25,6 +25,7 @@ import ViewModel from './viewmodel.js';
|
|||||||
import uAlert from './alert.js';
|
import uAlert from './alert.js';
|
||||||
import uDialog from './dialog.js';
|
import uDialog from './dialog.js';
|
||||||
import uObserve from './observe.js';
|
import uObserve from './observe.js';
|
||||||
|
import uTrack from './track.js';
|
||||||
import uUtils from './utils.js';
|
import uUtils from './utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,7 +158,10 @@ export default class MapViewModel extends ViewModel {
|
|||||||
if (track) {
|
if (track) {
|
||||||
uObserve.observe(track, 'positions', () => {
|
uObserve.observe(track, 'positions', () => {
|
||||||
this.displayTrack(track, false);
|
this.displayTrack(track, false);
|
||||||
this.api.zoomToExtent();
|
if (track instanceof uTrack && !this.api.isPositionVisible(track.length - 1)) {
|
||||||
|
console.log('last track position not visible');
|
||||||
|
this.api.centerToPosition(track.length - 1);
|
||||||
|
}
|
||||||
this.toggleStyleOptions();
|
this.toggleStyleOptions();
|
||||||
});
|
});
|
||||||
this.displayTrack(track, true);
|
this.displayTrack(track, true);
|
||||||
|
@ -301,6 +301,70 @@ describe('Google Maps map API tests', () => {
|
|||||||
expect(setTimeout).toHaveBeenCalledTimes(1);
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should center map to given marker', () => {
|
||||||
|
// given
|
||||||
|
const track = TrackFactory.getTrack(1);
|
||||||
|
const coordinates = [ 1, 3 ];
|
||||||
|
spyOn(GoogleMapsApi, 'getMarkerIcon');
|
||||||
|
spyOn(google.maps.Map.prototype, 'setCenter');
|
||||||
|
spyOn(google.maps.Marker.prototype, 'getPosition').and.returnValue(coordinates);
|
||||||
|
spyOn(google.maps, 'Marker').and.callThrough();
|
||||||
|
|
||||||
|
api.map = new google.maps.Map(container);
|
||||||
|
|
||||||
|
const id = 0;
|
||||||
|
api.setMarker(id, track);
|
||||||
|
// when
|
||||||
|
api.centerToPosition(id);
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(google.maps.Map.prototype.setCenter).toHaveBeenCalledWith(coordinates);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should confirm that position is visible', () => {
|
||||||
|
// given
|
||||||
|
const track = TrackFactory.getTrack(1);
|
||||||
|
const coordinates = [ 1, 3 ];
|
||||||
|
spyOn(GoogleMapsApi, 'getMarkerIcon');
|
||||||
|
spyOn(google.maps.Map.prototype, 'getBounds').and.returnValue(new google.maps.LatLngBounds());
|
||||||
|
spyOn(google.maps.LatLngBounds.prototype, 'contains').and.returnValue(true);
|
||||||
|
spyOn(google.maps.Marker.prototype, 'getPosition').and.returnValue(coordinates);
|
||||||
|
spyOn(google.maps, 'Marker').and.callThrough();
|
||||||
|
|
||||||
|
api.map = new google.maps.Map(container);
|
||||||
|
|
||||||
|
const id = 0;
|
||||||
|
api.setMarker(id, track);
|
||||||
|
// when
|
||||||
|
const result = api.isPositionVisible(id);
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(google.maps.LatLngBounds.prototype.contains).toHaveBeenCalledWith(coordinates);
|
||||||
|
expect(result).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should confirm that position is not visible', () => {
|
||||||
|
// given
|
||||||
|
const track = TrackFactory.getTrack(1);
|
||||||
|
const coordinates = [ 1, 3 ];
|
||||||
|
spyOn(GoogleMapsApi, 'getMarkerIcon');
|
||||||
|
spyOn(google.maps.Map.prototype, 'getBounds').and.returnValue(new google.maps.LatLngBounds());
|
||||||
|
spyOn(google.maps.LatLngBounds.prototype, 'contains').and.returnValue(false);
|
||||||
|
spyOn(google.maps.Marker.prototype, 'getPosition').and.returnValue(coordinates);
|
||||||
|
spyOn(google.maps, 'Marker').and.callThrough();
|
||||||
|
|
||||||
|
api.map = new google.maps.Map(container);
|
||||||
|
|
||||||
|
const id = 0;
|
||||||
|
api.setMarker(id, track);
|
||||||
|
// when
|
||||||
|
const result = api.isPositionVisible(id);
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(google.maps.LatLngBounds.prototype.contains).toHaveBeenCalledWith(coordinates);
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
it('should create marker from track position and add it to markers array', () => {
|
it('should create marker from track position and add it to markers array', () => {
|
||||||
// given
|
// given
|
||||||
const track = TrackFactory.getTrack(1);
|
const track = TrackFactory.getTrack(1);
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ol from '../src/lib/ol.js';
|
import * as ol from '../src/lib/ol.js';
|
||||||
import OpenlayersApi from '../src/mapapi/api_openlayers.js';
|
import OpenLayersApi from '../src/mapapi/api_openlayers.js';
|
||||||
import TrackFactory from './helpers/trackfactory.js';
|
import TrackFactory from './helpers/trackfactory.js';
|
||||||
import { config } from '../src/initializer.js'
|
import { config } from '../src/initializer.js'
|
||||||
import uLayer from '../src/layer.js';
|
import uLayer from '../src/layer.js';
|
||||||
@ -35,10 +35,14 @@ describe('Openlayers map API tests', () => {
|
|||||||
config.reinitialize();
|
config.reinitialize();
|
||||||
document.body.innerHTML = '';
|
document.body.innerHTML = '';
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
|
container.setAttribute('style', 'display: block; width: 100px; height: 100px');
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
mockViewModel = { mapElement: container, model: {} };
|
mockViewModel = { mapElement: container, model: {} };
|
||||||
api = new OpenlayersApi(mockViewModel, ol);
|
api = new OpenLayersApi(mockViewModel, ol);
|
||||||
mockMap = new ol.Map({ target: container });
|
mockMap = new ol.Map({
|
||||||
|
target: container,
|
||||||
|
view: new ol.View({ zoom: 8, center: [ 0, 0 ] })
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load and initialize api scripts', (done) => {
|
it('should load and initialize api scripts', (done) => {
|
||||||
@ -353,13 +357,12 @@ describe('Openlayers map API tests', () => {
|
|||||||
// given
|
// given
|
||||||
api.map = mockMap;
|
api.map = mockMap;
|
||||||
spyOn(ol.View.prototype, 'fit');
|
spyOn(ol.View.prototype, 'fit');
|
||||||
spyOn(ol.View.prototype, 'getZoom').and.returnValue(OpenlayersApi.ZOOM_MAX - 1);
|
spyOn(ol.View.prototype, 'getZoom').and.returnValue(OpenLayersApi.ZOOM_MAX - 1);
|
||||||
const extent = [ 0, 1, 2, 3 ];
|
const extent = [ 0, 1, 2, 3 ];
|
||||||
// when
|
// when
|
||||||
const result = api.fitToExtent(extent);
|
api.fitToExtent(extent);
|
||||||
// then
|
// then
|
||||||
expect(ol.View.prototype.fit).toHaveBeenCalledWith(extent, jasmine.any(Object));
|
expect(ol.View.prototype.fit).toHaveBeenCalledWith(extent, jasmine.any(Object));
|
||||||
expect(result).toEqual(extent);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fit to extent and zoom to max value', () => {
|
it('should fit to extent and zoom to max value', () => {
|
||||||
@ -368,7 +371,7 @@ describe('Openlayers map API tests', () => {
|
|||||||
const zoomedExtent = [ 3, 2, 1, 0 ];
|
const zoomedExtent = [ 3, 2, 1, 0 ];
|
||||||
api.map = mockMap;
|
api.map = mockMap;
|
||||||
spyOn(ol.View.prototype, 'fit');
|
spyOn(ol.View.prototype, 'fit');
|
||||||
spyOn(ol.View.prototype, 'getZoom').and.returnValue(OpenlayersApi.ZOOM_MAX);
|
spyOn(ol.View.prototype, 'getZoom').and.returnValue(OpenLayersApi.ZOOM_MAX);
|
||||||
spyOn(ol.View.prototype, 'setZoom');
|
spyOn(ol.View.prototype, 'setZoom');
|
||||||
spyOn(ol.View.prototype, 'calculateExtent').and.returnValue(zoomedExtent);
|
spyOn(ol.View.prototype, 'calculateExtent').and.returnValue(zoomedExtent);
|
||||||
// when
|
// when
|
||||||
@ -394,6 +397,68 @@ describe('Openlayers map API tests', () => {
|
|||||||
expect(marker.getGeometry().getFirstCoordinate()).toEqual(ol.proj.fromLonLat([ track.positions[0].longitude, track.positions[0].latitude ]));
|
expect(marker.getGeometry().getFirstCoordinate()).toEqual(ol.proj.fromLonLat([ track.positions[0].longitude, track.positions[0].latitude ]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should center map to given marker', () => {
|
||||||
|
// given
|
||||||
|
const track = TrackFactory.getTrack(1);
|
||||||
|
const coordinates = [ 1, 3 ];
|
||||||
|
track.positions[0].timestamp = 1;
|
||||||
|
track.positions[0].longitude = coordinates[0];
|
||||||
|
track.positions[0].latitude = coordinates[1];
|
||||||
|
const id = 0;
|
||||||
|
api.map = mockMap;
|
||||||
|
api.layerMarkers = new ol.layer.VectorLayer({ source: new ol.source.Vector() });
|
||||||
|
spyOn(ol.View.prototype, 'setCenter');
|
||||||
|
spyOn(api, 'getMarkerStyle');
|
||||||
|
|
||||||
|
api.setMarker(id, track);
|
||||||
|
// when
|
||||||
|
api.centerToPosition(id);
|
||||||
|
// then
|
||||||
|
expect(ol.View.prototype.setCenter).toHaveBeenCalledWith(ol.proj.fromLonLat(coordinates));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should confirm that position is visible', () => {
|
||||||
|
// given
|
||||||
|
const track = TrackFactory.getTrack(1);
|
||||||
|
const coordinates = [ 1, 3 ];
|
||||||
|
track.positions[0].timestamp = 1;
|
||||||
|
track.positions[0].longitude = coordinates[0];
|
||||||
|
track.positions[0].latitude = coordinates[1];
|
||||||
|
const id = 0;
|
||||||
|
api.map = mockMap;
|
||||||
|
api.layerMarkers = new ol.layer.VectorLayer({ source: new ol.source.Vector() });
|
||||||
|
spyOn(ol.extent, 'containsCoordinate').and.returnValue(true);
|
||||||
|
spyOn(api, 'getMarkerStyle');
|
||||||
|
|
||||||
|
api.setMarker(id, track);
|
||||||
|
// when
|
||||||
|
const result = api.isPositionVisible(id);
|
||||||
|
// then
|
||||||
|
expect(ol.extent.containsCoordinate).toHaveBeenCalledWith(jasmine.any(Array), ol.proj.fromLonLat(coordinates));
|
||||||
|
expect(result).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should confirm that position is not visible', () => {
|
||||||
|
// given
|
||||||
|
const track = TrackFactory.getTrack(1);
|
||||||
|
const coordinates = [ 1, 3 ];
|
||||||
|
track.positions[0].timestamp = 1;
|
||||||
|
track.positions[0].longitude = coordinates[0];
|
||||||
|
track.positions[0].latitude = coordinates[1];
|
||||||
|
const id = 0;
|
||||||
|
api.map = mockMap;
|
||||||
|
api.layerMarkers = new ol.layer.VectorLayer({ source: new ol.source.Vector() });
|
||||||
|
spyOn(ol.extent, 'containsCoordinate').and.returnValue(false);
|
||||||
|
spyOn(api, 'getMarkerStyle');
|
||||||
|
|
||||||
|
api.setMarker(id, track);
|
||||||
|
// when
|
||||||
|
const result = api.isPositionVisible(id);
|
||||||
|
// then
|
||||||
|
expect(ol.extent.containsCoordinate).toHaveBeenCalledWith(jasmine.any(Array), ol.proj.fromLonLat(coordinates));
|
||||||
|
expect(result).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
it('should get different marker style for start, end and normal position', () => {
|
it('should get different marker style for start, end and normal position', () => {
|
||||||
// given
|
// given
|
||||||
const track = TrackFactory.getTrack(3);
|
const track = TrackFactory.getTrack(3);
|
||||||
@ -499,7 +564,7 @@ describe('Openlayers map API tests', () => {
|
|||||||
// when
|
// when
|
||||||
api.zoomToExtent();
|
api.zoomToExtent();
|
||||||
// then
|
// then
|
||||||
expect(ol.View.prototype.fit).toHaveBeenCalledWith(extent, { maxZoom: OpenlayersApi.ZOOM_MAX });
|
expect(ol.View.prototype.fit).toHaveBeenCalledWith(extent, { padding: OpenLayersApi.TRACK_PADDING, maxZoom: OpenLayersApi.ZOOM_MAX });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get map bounds and convert to WGS84 (EPSG:4326)', () => {
|
it('should get map bounds and convert to WGS84 (EPSG:4326)', () => {
|
||||||
@ -510,7 +575,7 @@ describe('Openlayers map API tests', () => {
|
|||||||
// when
|
// when
|
||||||
const bounds = api.getBounds();
|
const bounds = api.getBounds();
|
||||||
// then
|
// then
|
||||||
expect(ol.View.prototype.calculateExtent).toHaveBeenCalledWith(jasmine.any(Array));
|
expect(ol.View.prototype.calculateExtent).toHaveBeenCalledTimes(1);
|
||||||
expect(bounds[0]).toBeCloseTo(20.597985430276808);
|
expect(bounds[0]).toBeCloseTo(20.597985430276808);
|
||||||
expect(bounds[1]).toBeCloseTo(52.15547181298076);
|
expect(bounds[1]).toBeCloseTo(52.15547181298076);
|
||||||
expect(bounds[2]).toBeCloseTo(21.363595171488573);
|
expect(bounds[2]).toBeCloseTo(21.363595171488573);
|
||||||
|
@ -53,6 +53,7 @@ export const setupGmapsStub = () => {
|
|||||||
this.sw = sw;
|
this.sw = sw;
|
||||||
this.ne = ne;
|
this.ne = ne;
|
||||||
}
|
}
|
||||||
|
contains() {/* ignore */}
|
||||||
extend() {/* ignore */}
|
extend() {/* ignore */}
|
||||||
getNorthEast() { return this.ne; }
|
getNorthEast() { return this.ne; }
|
||||||
getSouthWest() { return this.sw; }
|
getSouthWest() { return this.sw; }
|
||||||
|
@ -60,7 +60,9 @@ describe('MapViewModel tests', () => {
|
|||||||
'setTrackDefaultStyle': { /* ignored */ },
|
'setTrackDefaultStyle': { /* ignored */ },
|
||||||
'setTrackGradientStyle': { /* ignored */ },
|
'setTrackGradientStyle': { /* ignored */ },
|
||||||
'clearMap': { /* ignored */ },
|
'clearMap': { /* ignored */ },
|
||||||
'updateSize': { /* ignored */ }
|
'updateSize': { /* ignored */ },
|
||||||
|
'isPositionVisible': { /* ignored */ },
|
||||||
|
'centerToPosition': { /* ignored */ }
|
||||||
});
|
});
|
||||||
state = new uState();
|
state = new uState();
|
||||||
vm = new MapViewModel(state);
|
vm = new MapViewModel(state);
|
||||||
@ -251,7 +253,6 @@ describe('MapViewModel tests', () => {
|
|||||||
state.currentTrack.positions.push(TrackFactory.getPosition(100));
|
state.currentTrack.positions.push(TrackFactory.getPosition(100));
|
||||||
// then
|
// then
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(mockApi.zoomToExtent).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockApi.displayTrack).toHaveBeenCalledTimes(1);
|
expect(mockApi.displayTrack).toHaveBeenCalledTimes(1);
|
||||||
expect(mockApi.displayTrack).toHaveBeenCalledWith(track, false);
|
expect(mockApi.displayTrack).toHaveBeenCalledWith(track, false);
|
||||||
done();
|
done();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user