The js code below adds custom controls to openlayers map
/**
* Define a namespace for the application.
*/
window.app = {};
var app = window.app;
//
// Define rotate to north control.
//
/**
* #constructor
* #extends {ol.control.Control}
* #param {Object=} opt_options Control options.
*/
app.RotateNorthControl = function(opt_options) {
var options = opt_options || {};
var anchor = document.createElement('a');
anchor.href = '#rotate-north';
anchor.innerHTML = 'N';
var this_ = this;
var handleRotateNorth = function(e) {
// prevent #rotate-north anchor from getting appended to the url
e.preventDefault();
this_.getMap().getView().setRotation(0);
};
anchor.addEventListener('click', handleRotateNorth, false);
anchor.addEventListener('touchstart', handleRotateNorth, false);
var element = document.createElement('div');
element.className = 'rotate-north ol-unselectable';
element.appendChild(anchor);
ol.control.Control.call(this, {
element: element,
target: options.target
});
};
ol.inherits(app.RotateNorthControl, ol.control.Control);
//
// Create map, giving it a rotate to north control.
//
var map = new ol.Map({
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}).extend([
new app.RotateNorthControl()
]),
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
renderer: exampleNS.getRendererFromQueryString(),
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2,
rotation: 1
})
});
But I'm working on project using TypeScript instead javascript, and don't know how to make that work in typescript code.
Here's some part of code for openlayers map in typescript:
import openLayers = require('openLayers');
class OpenLayersMapProvider implements MapProvider {
map: openLayers.Map;
constructor(elementid: string, zoom: number = 8, tilesUrl?: string) {
var RotateNorthControl = function (opt_options) {
var options = opt_options || {};
var anchor = document.createElement('a');
anchor.href = '#rotate-north';
anchor.innerHTML = 'BUTTON';
var handleRotateNorth = function (e) {
prevent #rotate-north anchor from getting appended to the url
e.preventDefault();
this_.getMap().getView().setRotation(0);
};
anchor.addEventListener('click', null, false);
anchor.addEventListener('touchstart', null, false);
var element = document.createElement('div');
element.className = 'rotate-north ol-unselectable';
element.appendChild(anchor);
openLayers.control.Control.call(this, {
element: element,
target: this
});
//openLayers.inherits(RotateNorthControl(), openLayers.control.Control);
layers = [new openLayers.layer.Tile({
source: new openLayers.source.XYZ({
url: tilesUrl + '/{z}/{y}/{x}'
})
}),
this.vector, this.features]
this.map = new openLayers.Map({
target: elementid,
controls: openLayers.control.defaults().extend([
new openLayers.control.FullScreen(),
, new RotateNorthControl(???)
]),
interactions: openLayers.interaction.defaults().extend([this.select, this.modify]),
layers: layers,
view: new openLayers.View({
center: openLayers.proj.transform([9.66495667, 55.18794717], 'EPSG:4326', 'EPSG:3857'),
zoom: zoom
})
});
source: openLayers.source.Vector;
vector: openLayers.layer.Vector;
features: openLayers.layer.Vector;
}
export = OpenLayersMapProvider;
Does anyone know what is equivalent of window.app in typescript?
And how to do openLayers.inherits(RotateNorthControl(), openLayers.control.Control);? I only know that I need add something to the openlayers.d.ts file.
Thanx very much in advance for any help
A sample of custom control with typescript and ol3
export class MyControl extends ol.control.Control {
constructor() {
super({});
let button = document.createElement('button');
button.type = 'button';
button.className = 'ol-control';
button.innerHTML = 'N';
let element = document.createElement('div');
element.className = 'ol-feature ol-control';
element.appendChild(button);
ol.control.Control.call(this, {
element: element
});
button.addEventListener('click', () => this.click());
}
click() {
console.log('click');
console.log(this.getMap());
}
}
Does anyone know what is equivalent if window.app in typescript?
Hint : Define a namespace for the application.
Typescript supports this with the concept of module. So
JavaScript:
window.app = {};
var app = window.app;
app.RotateNorthControl = function(opt_options) {
would be TypeScript:
module app{
export var RotateNorthControl = function(opt_options) {
/// so on and so forth
}
how to do openLayers.inherits(RotateNorthControl(), openLayers.control.Control);
Use extends in TypeScript classes:
class RotateNorthControl extends openLayers.control.Control
You will need to rethink the purpose of RotateNorthControl from being a function to being a class.
Create a new class (as a separate TS-file) like so:
import { Control, defaults as defaultControls } from 'ol/control';
export class RotatControl extends Control {
constructor() {
super({});
var button = document.createElement('button');
button.innerHTML = 'N';
var element = document.createElement('div');
element.className = 'blahclass';
element.appendChild(button);
Control.call(this, {
element: element
});
button.addEventListener('click', this.rotate.bind(this), false);
}
rotate() {
super.getMap().getView().setRotation(0);
};
}
Then import this control in your component - you dont't need t feed the constructor any arguments at all.
Related
I'm trying to use DomMakers and DomIcons inside a ShadowRoot. When the markers load, I get this error:
Uncaught TypeError: Cannot read properties of undefined (reading 'getPropertyValue')
at rm.Ga (mapsjs-core.js:350:435)
at kn (mapsjs-core.js:376:338)
at S.Ga (mapsjs-core.js:376:52)
at T.de (mapsjs-core.js:408:437)
at $o (eval at <anonymous> (mapsjs-core.js:73:36), <anonymous>:5:219)
at Zo (eval at <anonymous> (mapsjs-core.js:73:36), <anonymous>:4:425)
at fp.g (eval at <anonymous> (mapsjs-core.js:73:36), <anonymous>:16:301)
This happens because mapsjs-core can't find the canvas element, since it's inside a ShadowRoot. Here is the code snippet where the error occurs:
var r = g.style;
f.push({
Aj: r.getPropertyValue(tm),
bo: r.getPropertyPriority(tm),
style: r
});
g is supposed to be the map canvas, but inside a ShadowRoot it's the document element
I'm using Here Maps API for Javascript v3.1.30.7 on a React app.
If I change to Markers and Icons the problem is gone, but I lose the interactivity I need.
It works only in this way:
https://jsfiddle.net/ba2oL057/2/
function drawCanvas() {
var canvas = document.createElement('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);
}
return canvas;
}
/**
* Create a marker that is capable of receiving DOM events and add it
* to the map.
*
* #param {H.Map} map A HERE Map instance within the application
*/
function addDomMarker(map) {
function setToShadow(outerElement){
var innerElement = document.createElement('div');
outerElement.attachShadow({mode: 'open'});
outerElement.shadowRoot.innerHTML = `
<div></div>
`;
let outerShElem = outerElement.shadowRoot.querySelectorAll('div')[0];
outerShElem.style.userSelect = 'none';
outerShElem.style.webkitUserSelect = 'none';
outerShElem.style.msUserSelect = 'none';
outerShElem.style.mozUserSelect = 'none';
outerShElem.style.cursor = 'default';
innerElement.style.color = 'red';
innerElement.style.backgroundColor = 'blue';
innerElement.style.border = '2px solid black';
innerElement.style.font = 'normal 12px arial';
innerElement.style.lineHeight = '12px'
innerElement.style.paddingTop = '2px';
innerElement.style.paddingLeft = '4px';
innerElement.style.width = '20px';
innerElement.style.height = '20px';
// add negative margin to inner element
// to move the anchor to center of the div
innerElement.style.marginTop = '-10px';
innerElement.style.marginLeft = '-10px';
outerShElem.appendChild(innerElement);
outerShElem.appendChild(drawCanvas());
// Add text to the DOM element
innerElement.innerHTML = 'C';
outerShElem.addEventListener('mouseover', changeOpacity);
outerShElem.addEventListener('mouseout', changeOpacityToOne);
}
var outerElement = document.createElement('div');
//document.body.appendChild(outerElement);
function changeOpacity(evt) {
evt.target.style.opacity = 0.6;
};
function changeOpacityToOne(evt) {
evt.target.style.opacity = 1;
};
//create dom icon and add/remove opacity listeners
var domIcon = new H.map.DomIcon(outerElement, {
// the function is called every time marker enters the viewport
onAttach: function(clonedElement, domIcon, domMarker) {
setToShadow(clonedElement);
},
// the function is called every time marker leaves the viewport
onDetach: function(clonedElement, domIcon, domMarker) {
let outerShElem = clonedElement.shadowRoot.querySelectorAll('div')[0];
outerShElem.removeEventListener('mouseover', changeOpacity);
outerShElem.removeEventListener('mouseout', changeOpacityToOne);
}
});
// Marker for Chicago Bears home
var bearsMarker = new H.map.DomMarker({lat: 41.8625, lng: -87.6166}, {
icon: domIcon
});
map.addObject(bearsMarker);
//setTimeout(()=>{map.removeObject(bearsMarker);}, 0);
}
/**
* Boilerplate map initialization code starts below:
*/
//Step 1: initialize communication with the platform
// In your own code, replace variable window.apikey with your own apikey
var platform = new H.service.Platform({
apikey: window.apikey
});
var defaultLayers = platform.createDefaultLayers();
//Step 2: initialize a map - this map is centered over Chicago.
var map = new H.Map(document.getElementById('map'),
defaultLayers.vector.normal.map,{
center: {lat:41.881944, lng:-87.627778},
zoom: 11,
pixelRatio: window.devicePixelRatio || 1
});
// add a resize listener to make sure that the map occupies the whole container
window.addEventListener('resize', () => map.getViewPort().resize());
//Step 3: make the map interactive
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
// Now use the map as required...
addDomMarker(map);
I am trying to add a custom Geolocate me button to my map and it kind of works, however only if I also add the standard icon from Mapbox. The code below works, but if I remove the line map.addControl(geolocate, 'top-right');, my left button stops working.
// Initialize the geolocate control.
var geolocate = new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
});
// Add the control to the map.
map.addControl(geolocate, 'top-right');
class ToggleControl {
constructor(options) {
this._options = Object.assign({}, this._options, options)
}
onAdd(map, cs) {
this.map = map;
this.container = document.createElement('div');
this.container.className = `${this._options.className}`;
const button = this._createButton('monitor_button')
this.container.appendChild(button);
return this.container;
}
onRemove() {
this.container.parentNode.removeChild(this.container);
this.map = undefined;
}
_createButton(className) {
const el = window.document.createElement('button')
el.className = className;
el.textContent = 'Use my location';
el.addEventListener('click', (e) => {
geolocate.trigger();
}, false)
return el;
}
}
const toggleControl = new ToggleControl({
className: 'mapboxgl-ctrl'
})
map.addControl(toggleControl, 'top-left')
screenshot - in blue is what I want to keep, in Red to remove
Instead of creating new mapboxgl.GeolocateControl Instance, you can extend mapboxgl.GeolocateControl Class like below:
class ToggleControl extends mapboxgl.GeolocateControl {
_onSuccess(position) {
this.map.flyTo({
center: [position.coords.longitude, position.coords.latitude],
zoom: 17,
bearing: 0,
pitch: 0
});
}
onAdd(map, cs) {
this.map = map;
this.container = document.createElement('div');
this.container.className = `mapboxgl-ctrl`;
const button = this._createButton('monitor_button')
this.container.appendChild(button);
return this.container;
}
_createButton(className) {
const el = window.document.createElement('button')
el.className = className;
el.textContent = 'Use my location';
el.addEventListener('click', () => {
this.trigger();
});
this._setup = true;
return el;
}
}
const toggleControl = new ToggleControl({})
map.addControl(toggleControl, 'top-left')
Other Helpful links:
Mapbox gl Class URL: Have a look https://github.com/mapbox/mapbox-gl-js/blob/520a4ca3b9d260693f1e7f4af30d8b91c8f8bb08/src/ui/control/geolocate_control.js#L97-L565
How to wrap a ui control (mapbox geolocation control)
You can easily add custom button in mapBox .
add some css:
.map_btn{position: absolute; top: 25; width: 36px;height:36px;border-radius:50%;border:none;float:right;margin-right:5px;zindex:111;left:85%;background:#e8faa7;}
add the button
<button class="map_btn"><img src="loc1.png" width=30/>
</button>
<div id="map"></div>
Add your code to your button
$('.map_btn').click(function(e)
{
app.preloader.show();
getLocation() ;
});
I use OpenLayers map and I would like to get some text from var informations after click to marker. I think thant I need to have "general" function about singleclick in cycle for, because I need do it for each index [i]. But If I click to some marker I don´t see any information.
I tried to move "general" function to down before console.table(window.phpdata.markers). When I run it, I can see last text from "informations" after click on each marker --> there isn´t any problem with getting data from databases.
(This isn´t required result because I don´t want to see last marker. I would like to see relevant text from informations for relevant marker. So with same index [i].
What to do for this result? Thank´s
Object.defineProperty(window.phpdata, "markers", {value: <?php echo !empty($list) ? json_encode($list) : 'null'; ?>, writable: false, configurable: false});
var base = new ol.layer.Tile({
source: new ol.source.OSM()
});
var map = new ol.Map({
target: 'map',
layers: [base],
view: new ol.View({
center: ol.proj.fromLonLat([-74.0061,40.712]), zoom: 2
})
});
var vectors = new ol.source.Vector({});
if(window.phpdata.markers && window.phpdata.markers.length > 0) {
var data = window.phpdata.markers;
for(var i = 0; i < data.length; i++) {
var informations = data[i].info;
var marker = new ol.Feature({
geometry: new ol.geom.Point(
ol.proj.fromLonLat([Number(data[i].lng), Number(data[i].lat)])
),
});
marker.setStyle(new ol.style.Style({
image: new ol.style.Icon(({
src: 'dot.png'
}))
}));
vectors.addFeature(marker);
marker.on('singleclick', function (event) { //general function to move..
if (map.hasFeatureAtPixel(event.pixel) === true) {
document.getElementById("www").innerHTML=informations;
} else {
overlay.setPosition(undefined);
closer.blur();
}
}); // //to there
}
var layer = new ol.layer.Vector({
source: vectors,
});
map.addLayer(layer);
}
document.getElementById('myposition').innerText = "click to map and get coordinates here";
map.on('singleclick', event => {
const coordinate = ol.proj.toLonLat(event.coordinate);
//const innerText = `Lon: ${coordinate[0].toFixed(4)}, Lat: ${coordinate[1].toFixed(4)}`;
const innerText = `${coordinate[0].toFixed(4)}, ${coordinate[1].toFixed(4)}`;
document.getElementById('myposition').innerText = innerText;
});
map.on('pointermove', function(evt) {
map.getTargetElement().style.cursor = map.hasFeatureAtPixel(evt.pixel) ? 'pointer' : '';
});
console.table(window.phpdata.markers)
Features do not have click events. You need to check if there are features where you click the map. You can add the info as a property to the feature and use get() to display it.
for(var i = 0; i < data.length; i++) {
var marker = new ol.Feature({
geometry: new ol.geom.Point(
ol.proj.fromLonLat([Number(data[i].lng), Number(data[i].lat)])
),
info: data[i].info
});
}
....
....
map.on('click', function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature) {
return feature;
});
if (feature) {
document.getElementById("www").innerHTML = feature.get('info');
....
};
});
I want to get the Feature of a Layer by coordinate.
Furthermore I want to open this feature in a popup, which I have solved so far by an onclick event. But I want to realize by giving the coordinates of a feature and opening the popup of the featue.
I have a layer with the map and a layer with the features:
if (trackMap != null) {
for (var i = 0; i < trackMap.length; i++) {
var trackInfo = trackMap[i];
lat = parseFloat(trackInfo.lat);
lon = parseFloat(trackInfo.lon);
var layergpx = new ol.layer.Vector({
source: new ol.source.Vector({
parser: new ol.parser.GPX(),
url: '${contextRoot}/gps/gpx2' + trackInfo.url
})
});
layers.push(layergpx);
}
}
I want to get the feature of this layer in another Javascript function.
How I open a pop up by clicking on the map:
/**
* The Click Event to show the data
*/
var element = document.getElementById('popup');
var popup = new ol.Overlay({
element: element,
positioning: ol.OverlayPositioning.BOTTOM_CENTER,
stopEvent: false
});
map.addOverlay(popup);
map.on('singleclick', function(evt) {
map.getFeatures({
pixel: evt.getPixel(),
layers: vectorLayers,
success: function(layerFeatures) {
var feature = layerFeatures[0][0];
if (feature) {
var geometry = feature.getGeometry();
var coord = geometry.getCoordinates();
popup.setPosition(coord);
$(element).popover({
'placement': 'top',
'html': true,
'content': feature.get('desc')
});
$(element).popover('show');
} else {
$(element).popover('destroy');
}
}
});
});
But I want this feature not to be opened by clicking on it on the map, but by entering a coordinate in a textfield and the map opens this pop up, like in the onclick event.
Take a look at this example to see if it helps you:
http://openlayers.org/en/latest/examples/kml.html
var displayFeatureInfo = function(pixel) {
map.getFeatures({
pixel: pixel,
layers: [vector],
success: function(featuresByLayer) {
var features = featuresByLayer[0];
var info = [];
for (var i = 0, ii = features.length; i < ii; ++i) {
info.push(features[i].get('name'));
}
document.getElementById('info').innerHTML = info.join(', ') || ' ';
}
});
map.getFeatures() has this success callback where it delivers the features of the layers specified in layers: [vector]. Customize it at will to get what you need.
=== Update ===
In the OpenLayers 3's Map object you have a function: getPixelFromCoordinate
/**
* #param {ol.Coordinate} coordinate Coordinate.
* #return {ol.Pixel} Pixel.
*/
ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
var frameState = this.frameState_;
if (goog.isNull(frameState)) {
return null;
} else {
var vec2 = coordinate.slice(0, 2);
return ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, vec2, vec2);
}
};
I'm trying to use MarkerClusterer to clusterize the markers on my map.
The problem is that I'm not using default markers (google.maps.Marker), but instead a custom class which hinerits from google.maps.OverlayView.
Unfortunately it seems that the library has been developed assuming the use of basic markers, in fact I get errors because my class does not implement methods defined in google.maps.Marker.
Is it possible to use the MarkerClusterer by keeping my custom markers?
EDIT: it was a lot easier than I expected, I solved by implementing 2 methods in my custom class:
setVisible() and getPosition()
in order to help others the following is my complete interface (without full implementation):
BFPushpin = function(config)
{
this.setMap(config.map);
this.set("position", config.position);
// other settings...
};
// my class extends google.maps.OverlayView
BFPushpin.prototype = new google.maps.OverlayView();
BFPushpin.prototype.getBounds = function()
{
return new google.maps.LatLngBounds(this.position, this.position);
};
BFPushpin.prototype.getPoint = function()
{
var bounds = this.getBounds();
var projection = this.getProjection();
var sw = projection.fromLatLngToDivPixel(bounds.getSouthWest());
var ne = projection.fromLatLngToDivPixel(bounds.getNorthEast());
return new google.maps.Point(sw.x, ne.y);
};
BFPushpin.prototype.getSuperContainer = function()
{
var panes = this.getPanes();
return jQuery(panes ? panes.overlayImage : "");
};
BFPushpin.prototype.getContainer = function()
{
// return inner container
};
BFPushpin.prototype._generatePopupContent = function()
{
// return markup for the popupwindow
};
BFPushpin.prototype._addListeners = function()
{
// add handlers for the pushpin
};
BFPushpin.prototype.onAdd = function()
{
// customize content here
};
BFPushpin.prototype.onRemove = function()
{
// remove pin container here
};
BFPushpin.prototype.draw = function()
{
// set display style here
};
BFPushpin.prototype.setVisible = function(visible)
{
// set display block or hidden
};
BFPushpin.prototype.getPosition = function()
{
return this.position;
};
Or just define the functions that the MarkerClusterer expects on the marker. setMap and getPosition() and some other ones.
You should probably define your new marker class in such a way that it also inherits from google.maps.Marker (i.e. that it implements its interface). It is logical that MarkerClusterer uses this interface - it has to suppose the markers are markers in order to work with them :-)
Hopefully this will also help people trying to get this solution to work. Thanks #daveoncode for the example. I was able to modify it to get it working for me:
renderMap() {
const mapProperties = {
center: new google.maps.LatLng(34.0234, -84.6155),
zoom: 10,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
this.map = new google.maps.Map(this.mapElement.nativeElement, mapProperties);
let markers = [];
for (let i=0; i<this._sitesList.length; i++) {
let bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(this._sitesList[i].lat, this._sitesList[i].lon)
);
let position = new google.maps.LatLng(this._sitesList[i].lat, this._sitesList[i].lon);
let html = this.makeHtmlForSitePanel(i, this._sitesList[i]); // I have a function to return html for my OverlayView panels here.
markers.push( this.generateSitePanel(bounds, html, this.map, position) );
}
var markerCluster = new MarkerClusterer(this.map, markers, {
imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'
});
}
generateSitePanel(bounds, html, map, position) {
SitePanel.prototype = new google.maps.OverlayView();
function SitePanel (bounds, html, map, position) {
this.bounds_ = bounds;
this.set('position', position);
this.html_ = html;
this.map_ = map;
this.div_ = null;
this.setMap(map);
}
SitePanel.prototype.getBounds = function() {
return new google.maps.LatLngBounds(this.position, this.position);
};
SitePanel.prototype.getPoint = function() {
var bounds = this.getBounds();
var projection = this.getProjection();
var sw = projection.fromLatLngToDivPixel(bounds.getSouthWest());
var ne = projection.fromLatLngToDivPixel(bounds.getNorthEast());
return new google.maps.Point(sw.x, ne.y);
};
SitePanel.prototype.getSuperContainer = function(){
var panes = this.getPanes();
return $(panes ? panes.overlayImage : '');
};
SitePanel.prototype.getContainer = function()
{
// return inner container
// I don't have anything for this one
};
SitePanel.prototype.getPosition = function() {
return this.position;
};
SitePanel.prototype.onAdd = function() {
var div = document.createElement('div');
div.className = 'tooltip-container-';
div.innerHTML = this.html_;
div.style.position = 'absolute';
this.div_ = div;
var panes = this.getPanes();
panes.overlayImage.appendChild(div);
};
SitePanel.prototype.draw = function() {
var overlayProjection = this.getProjection();
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 20 + 'px';
};
SitePanel.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
};
return new SitePanel(bounds, html, map, position);
}