OpenLayers 4 ruler - javascript

I'm refactoring the whole code of the app with modules and I'm having some problem with the Ruler function of OpenLayers 4 now.
In the previous code was working OK, but now when I double click on the map to stop the current measure and start a new one, the only thing left in the screen is the popup, the line (the line of what I was measuring before) is removed.
Here my code:
const initVector = (mapView) => {
/**
* vector layers sources
*/
$('.form-inline').click(function () {
addRuler()
alert("initialize")
})
let Rsource = new ol.source.Vector()
let rulerLayer = new ol.layer.Vector({
source: Rsource,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.2)'
}),
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 0, 0.7)',
width: 3
}),
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: 'rgba(0, 0, 0, 0.7)'
})
})
})
})
/*
* ruler tool handler
*/
/**
* global vars
*/
// this style is done by javascript to bypass the override rule that enter in conflict with FRA
$('.markers button').css({'margin': '5px 7px', 'padding': '0 10px'})
$('#markloc').css('margin-left', '5px')
// these var are for the creation of the text and all the related element during the measure
let sketch
let helpTooltipElement
let helpTooltip
let measureTooltipElement
let measureTooltip
let ruler
/*
* pointer handler
*/
const pointerMoveHandler = (evt) => {
/*
* if the mouse is dragging the map return
*/
if (evt.dragging) {
return
}
/**
* default message to display
*/
let helpMsg = 'Click to start drawing'
/**
* check the message if you are measuring
*/
if (sketch) {
helpMsg = 'Click to continue drawing the line or double click to stop.'
}
/**
* attach to the tooltip the correct message
* set the position near the mouse cursor
* display the tooltip
*/
helpTooltipElement.innerHTML = helpMsg
helpTooltip.setPosition(evt.coordinate)
helpTooltipElement.classList.remove('hidden')
}
/**
* display the actual measured length
*/
function formatLength (line) {
const length = ol.Sphere.getLength(line)
let output
if (length > 100) {
output = `${Math.round(length / 1000 * 100) / 100} km`
}
else {
output = `${Math.round(length * 100) / 100} m`
}
return output
}
/**
* create a new tooltip
*/
function createHelpTooltip () {
helpTooltipElement = document.createElement('div')
helpTooltipElement.className = 'tooltip hidden'
helpTooltip = new ol.Overlay({
element: helpTooltipElement,
offset: [15, 0],
positioning: 'center-left'
})
mapView.addOverlay(helpTooltip)
}
/**
* Creates a new measure tooltip
*/
function createMeasureTooltip () {
measureTooltipElement = document.createElement('div')
measureTooltipElement.className = 'tooltip tooltip-measure'
measureTooltip = new ol.Overlay({
element: measureTooltipElement,
offset: [0, -15],
positioning: 'bottom-center'
})
mapView.addOverlay(measureTooltip)
}
/**
* add the ruler when you click on the button
*/
function addRuler () {
/**
* add a selected class to the ruler button to make it visible that it's in use
*/
$('#tlruler').addClass('custbtnsel')
/**
* styling ruler
*/
ruler = new ol.interaction.Draw({
source: Rsource,
type: 'LineString',
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 0, 0.5)',
lineDash: [10, 10],
width: 2
}),
image: new ol.style.Circle({
radius: 5,
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 0, 0.7)'
})
})
})
})
/**
* call the pointerMoveHandler to create the element on the screen when the ruler it's in use
*/
mapView.on('pointermove', pointerMoveHandler)
/**
* mouseout event listener to hidden the popup
*/
mapView.getViewport().addEventListener('mouseout', () => {
helpTooltipElement.classList.add('hidden')
})
/**
* add the ruler interaction to the map
*/
mapView.addInteraction(ruler)
/**
* create the tooltip
*/
createMeasureTooltip()
createHelpTooltip()
let listener
/**
* drawstart event
*/
ruler.on('drawstart', function (evt) {
// set sketch
sketch = evt.feature
// tooltip coordinate
let tooltipCoord = evt.coordinate
/**
* sketch event listener on change
* called during a mouse move
*/
listener = sketch.getGeometry().on('change', function (evt) {
let geom = evt.target
/**
* as we don't use polygon we check justfor line
* get last position of the cursor and the length
*/
let output
// OL 5 CODE
// if (geom instanceof LineString)
if (geom instanceof ol.geom.LineString) {
output = formatLength(geom)
tooltipCoord = geom.getLastCoordinate()
}
/**
* append to the tooltip the measure
* set the position of the tooltip to the last cursor coord
*/
measureTooltipElement.innerHTML = output
measureTooltip.setPosition(tooltipCoord)
})
}, this)
/**
* drawend event
*/
ruler.on('drawend', () => {
/**
* create the static tooltip with the last measure
*/
console.log('drawend')
measureTooltipElement.className = 'tooltip tooltip-static'
measureTooltip.setOffset([0, -7])
/**
* set sketch and the tooltip element to null
*/
sketch = null
measureTooltipElement = null
/**
* set sketch and the tooltip element to null
*/
createMeasureTooltip()
// OL 5 code
// unByKey(listener);
ol.Observable.unByKey(listener)
}, this)
/**
* end addRuler function
*/
}
}
const mapLayer = new ol.layer.Tile({
source: new ol.source.OSM()
});
let map = new ol.Map({
layers: [mapLayer],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 10
})
})
initVector(map)
the link to the pen so you can test it
https://codepen.io/sebalaini/pen/OrRELp?editors=0010
and here how should work:
https://openlayers.org/en/latest/examples/measure.html
Notice that I created a function to initialise the ruler to simulate the module, as my map is created inside a module and the ruler in another, in the map module then I import that function and I initialise it with the map variable

Thanks to #Mike that answer me in Gis.stackexchange.
In initVector after creating rulerLayer you need to add it to the map map.addLayer(rulerLayer);
A very small error.

Related

Javascript canvas hover over a tile

I have a canvas which I am drawing from certain tiles (new Image()), lets say there are for example 10x10 tiles in my canvas.
Every tile has its own data I want to display "popup" with information about this tile everytime user hovers over the tile with mouse, it should be following the mouse until user leaves the tile.
Example of what I want to achieve would be google maps, but it should be following the mouse as I move the mouse over the tile:
https://i.gyazo.com/d32cd5869ae9b2e0d9a7053729e2d2aa.mp4
You will need three things to achieve this:
A way to draw the background image on your canvas
A way to track mouse position over the canvas
A list of "zones" and a way to determine which zone is "triggered"
Here is an implementation of the above points:
var GridOverlay = /** #class */ (function () {
/**
* Creates an instance of GridOverlay.
* #param {string} imageSrc
* #param {{
* position: IPoint,
* size: IPoint,
* message: string
* }[]} [zones=[]]
* #memberof GridOverlay
*/
function GridOverlay(imageSrc, zones) {
if (zones === void 0) { zones = []; }
var _this = this;
this.zones = zones;
/**
* The last registered position of the cursor
*
* #type {{ x: number, y: number }}
* #memberof GridOverlay
*/
this.mousePosition = { x: 0, y: 0 };
//Create an image element
this.img = document.createElement("img");
//Create a canvas element
this.canvas = document.createElement("canvas");
//When the image is loaded
this.img.onload = function () {
//Scale canvas to image
_this.canvas.width = _this.img.naturalWidth;
_this.canvas.height = _this.img.naturalHeight;
//Draw on canvas
_this.draw();
};
//Set the "src" attribute to begin loading
this.img.src = imageSrc;
//Listen for "mousemove" on our canvas
this.canvas.onmousemove = this.mouseMove.bind(this);
}
/**
* Registers the current position of the cursor over the canvas, saves the coordinates and calls "draw"
*
* #param {MouseEvent} evt
* #memberof GridOverlay
*/
GridOverlay.prototype.mouseMove = function (evt) {
this.mousePosition.x = evt.offsetX;
this.mousePosition.y = evt.offsetY;
this.draw();
};
/**
* Clears and redraws the canvas with the latest data
*
* #memberof GridOverlay
*/
GridOverlay.prototype.draw = function () {
//Get drawing context
var ctx = this.canvas.getContext("2d");
//Clear canvas
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
//Draw background
ctx.drawImage(this.img, 0, 0);
//Loop through zones
for (var zoneIndex = 0; zoneIndex < this.zones.length; zoneIndex++) {
var zone = this.zones[zoneIndex];
//Check for cursor in zone
if (zone.position.x <= this.mousePosition.x &&
zone.position.x + zone.size.x >= this.mousePosition.x &&
zone.position.y <= this.mousePosition.y &&
zone.position.y + zone.size.y >= this.mousePosition.y) {
//Display zone message on cursor position
ctx.fillText(zone.message, this.mousePosition.x, this.mousePosition.y);
//Break so that we only show a single message
break;
}
}
return this;
};
return GridOverlay;
}());
//TEST
var grid = new GridOverlay("https://placeholdit.imgix.net/~text?txtsize=60&txt=1&w=500&h=500", [
{ message: "Zone 1", position: { x: 10, y: 10 }, size: { x: 100, y: 100 } },
{ message: "Zone 2", position: { x: 80, y: 80 }, size: { x: 100, y: 100 } },
]);
document.body.appendChild(grid.canvas);

minimal size for a rectangle in style

I'm writing an application where users can mark regions on a world map. Now these regions can be very small, so that it's hard to click on them when not zoomed in.
Is there a way how I can define (e.g. in the style function) that a (rectangle) feature should always be rendered with at least e.g. 10px × 10px?
Update: some code I currently use:
on the drawing side:
var draw = new ol.interaction.Draw({
source: vectorSource,
type: 'LineString',
geometryFunction: function(coordinates, geometry) {
if(!geometry) {
geometry = new ol.geom.Polygon(null);
}
var start = coordinates[0];
var end = coordinates[1];
geometry.setCoordinates([[
start,
[start[0], end[1]],
end,
[end[0], start[1]],
start
]]);
return geometry;
},
maxPoints: 2
});
draw.on('drawend', function(e) {
var extent = e.feature.getGeometry().getExtent();
extent = app.map.rlonlate(extent); // own function to convert it from map coordinates into lat/lon
// some code to save the extent to the database
});
and on the displaying side:
vectorSource.addFeature(
new ol.Feature({
geometry: ol.geom.Polygon.fromExtent(app.map.lonlate(extent)),
// … some more custom properties like a display name …
})
);
the style function:
function(feature) {
return [new ol.style.Style({
stroke: new ol.style.Stroke({
color: feature.get('mine') ? '#204a87' : '#729fcf',
width: 2
}),
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, ' + (feature.get('mine') ? '0.5' : '0.2') + ')'
})
})];
}
Using a style function is a good idea. The second argument of the style function is resolution which you can use to check if your feature geometry would be smaller than e.g. 10 px at the current resolution:
var styleFn = function(feature, resolution) {
var minSizePx = 30;
var minSize = minSizePx * resolution;
var extent = feature.getGeometry().getExtent();
if (ol.extent.getWidth(extent) < minSize || ol.extent.getHeight(extent) < minSize) {
// special style for polygons that are too small
var center = new ol.geom.Point(ol.extent.getCenter(extent));
return new ol.style.Style({
geometry: center,
image: ...
} else {
// normal style
return defaultStyle;
}
};
http://jsfiddle.net/ukc0nmy2/1/

DragBox change color while drawing

In order to help users to draw a boxarea with minimum of 100 px at width and height, I've thought to start drawing in red color (fill and border of box) and then change automatically to green when it reaches the 100 px mentioned while user is drawing the feature.
Any idea how to do this?
I got it something like that when user has finished drawing, but in my opinion, that behavior is not enough comfortable.
Thanks in advance
UPDATE:
http://jsfiddle.net/jonataswalker/41j800kv/
Found a better solution. Put these conditions inside a ol.interaction.Draw#StyleFunction:
var draw = new ol.interaction.Draw({
source: vectorSource,
type: 'LineString',
maxPoints: 2,
style: function(feature){
var style;
var geometry = feature.getGeometry();
var extent = geometry.getExtent();
var topLeft =
map.getPixelFromCoordinate(ol.extent.getTopLeft(extent));
var bottomLeft =
map.getPixelFromCoordinate(ol.extent.getBottomLeft(extent));
var topRight =
map.getPixelFromCoordinate(ol.extent.getTopRight(extent));
var width = topRight[0] - topLeft[0];
var height = bottomLeft[1] - topLeft[1];
coords_element.innerHTML =
'width: ' + width + '<br>height: ' + height;
if (width > 100 && height > 100) {
style = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.2)'
}),
stroke: new ol.style.Stroke({
color: 'red',
width: 2
})
});
} else {
style = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.2)'
}),
stroke: new ol.style.Stroke({
color: '#ffcc33',
width: 2
})
});
}
return [style];
},
geometryFunction: function(coordinates, geometry) {
if (!geometry) {
geometry = new ol.geom.Polygon(null);
}
var start = coordinates[0];
var end = coordinates[1];
geometry.setCoordinates([
[start, [start[0], end[1]], end, [end[0], start[1]], start]
]);
return geometry;
}
});
Take this piece of code and put some conditions on it:
Using the latest version of ol3. 3.13.1 you may do the following to achieve your goal.
Create a map with a layer and add a dragbox interaction
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var map = new ol.Map({
layers: [raster],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
var selectOnBoxInt = new ol.interaction.DragBox({
condition : ol.events.condition.always
});
map.addInteraction(selectOnBoxInt);
//set it active on start up
selectOnBoxInt.setActive(true);
Create two css classes holding styles for your drawbox
//this is the deafult
.ol-dragbox {
background-color: rgba(255,0,0,0.4);
border-color: rgba(2500,0,0,1);
border-width:2;
}
//this is when width,height>100
.ol-mydragbox {
background-color: rgba(0,255,0,0.4);
border-color: rgba(0,255,0,1);
border-width:2;
}
asign a boxdrag event to your drawbox interaction so you can truck down its width, height and make the style changes. For this action, and for the sake of time, I use jquery. You may use your imagination to do it without jquery.
selectOnBoxInt.on('boxdrag',function(e){
var width = Math.abs(e.target.box_.endPixel_[0] - e.target.box_.startPixel_[0]);
var height = Math.abs(e.target.box_.endPixel_[1] - e.target.box_.startPixel_[1]);
if (width>100 && height>100){
$('.ol-dragbox').removeClass('ol-dragbox').addClass('ol-mydragbox');
$('.ol-box').removeClass('ol-box').addClass('ol-mydragbox');
} else {
$('.ol-mydragbox').removeClass('ol-mydragbox').addClass('ol-dragbox');
}
});
And a fiddle to see it in action.

How to Break up large Javascript file with RequireJS

I'm new to Javascript and therefore requireJS - I'm currently teaching myself using Openlayers 3 examples, of which, I've just been appending to one large JS file. Seeing that this is becoming unruly very quickly, I read up about RequireJS and thought I should get into the habit of doing things right from the onset; 'which is where I've hit issues'.
[Not that I imagine it matters, but i'm using Asp.net MVC]
Basically, I wish to break the file up into smaller related modules e.g.
Map [which is used by all modules and initiates the base layer map]
Draw [handles points / polygons etc. and is added to the map as
another layer]
Geolocation [contains geolocation functions for plotting]
etc., etc.
...giving the flexibility to have all layers activated at once, or a select few with easy to manage JS code.
I have had several attempts at breaking this code up into such individual JS files, [map / draw / Geolocation] and all fail as I feel I'm not grasping the requireJS methodology (so as not to confuse readers and myself further, I'm neglecting to add my attempts).
Here is the basic code that works:
require.config({
baseUrl: "/Scripts",
paths: {
//jquery: "/lib/jquery-1.11.1.min",
ol: [
"http://openlayers.org/en/v3.8.1/build/ol",
"/lib/ol"
],
domReady: "/lib/domReady"
},
//map: { main: { test: "/Modules/Test/scripts/test" } },
//The shim section is to tell RequireJS about any dependencies your files have before they can be used.
//Here, we are saying if we call “ol” to load that module, we have to load “jquery” first.
//shim: {
//ol: ["jquery"]
//},
//packages: [
// {
//name: 'test',
//location: 'http://...
//main: 'main'
//}]
});
File I wish to break-up:
define(["ol"], function (ol) {
$(document).ready(function () {
//****************
//------MAP-------
//Setup Map Base
// creating the view
var view = new ol.View({
center: ol.proj.fromLonLat([5.8713, 45.6452]),
zoom: 19
});
// creating the map
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: "map",
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}),
view: view
});
//****************
//-----DRAW------
var features = new ol.Collection();
var featureOverlay = new ol.layer.Vector({
source: new ol.source.Vector({ features: features }),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(255, 255, 255, 0.2)"
}),
stroke: new ol.style.Stroke({
color: "#ffcc33",
width: 2
}),
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: "#ffcc33"
})
})
})
});
featureOverlay.setMap(map);
var modify = new ol.interaction.Modify({
features: features,
// the SHIFT key must be pressed to delete vertices, so
// that new vertices can be drawn at the same position
// of existing vertices
deleteCondition: function (event) {
return ol.events.condition.shiftKeyOnly(event) &&
ol.events.condition.singleClick(event);
}
});
map.addInteraction(modify);
var draw; // global so we can remove it later
function addInteraction() {
draw = new ol.interaction.Draw({
features: features,
type: /** #type {ol.geom.GeometryType} */ (typeSelect.value)
});
map.addInteraction(draw);
}
var typeSelect = document.getElementById("type");
/**
* Let user change the geometry type.
* #param {Event} e Change event.
*/
typeSelect.onchange = function (e) {
map.removeInteraction(draw);
addInteraction();
};
addInteraction();
//****************
//---GEOLOCATION---//
// Common app code run on every page can go here
// Geolocation marker
var markerEl = document.getElementById("geolocation_marker");
var marker = new ol.Overlay({
positioning: "center-center",
element: markerEl,
stopEvent: false
});
map.addOverlay(marker);
// LineString to store the different geolocation positions. This LineString
// is time aware.
// The Z dimension is actually used to store the rotation (heading).
var positions = new ol.geom.LineString([],
/** #type {ol.geom.GeometryLayout} */ ("XYZM"));
// Geolocation Control
var geolocation = new ol.Geolocation( /** #type {olx.GeolocationOptions} */({
projection: view.getProjection(),
trackingOptions: {
maximumAge: 10000,
enableHighAccuracy: true,
timeout: 600000
}
}));
var deltaMean = 500; // the geolocation sampling period mean in ms
// Listen to position changes
geolocation.on("change", function (evt) {
var position = geolocation.getPosition();
var accuracy = geolocation.getAccuracy();
var heading = geolocation.getHeading() || 0;
var speed = geolocation.getSpeed() || 0;
var m = Date.now();
addPosition(position, heading, m, speed);
var coords = positions.getCoordinates();
var len = coords.length;
if (len >= 2) {
deltaMean = (coords[len - 1][3] - coords[0][3]) / (len - 1);
}
var html = [
"Position: " + position[0].toFixed(2) + ", " + position[1].toFixed(2),
"Accuracy: " + accuracy,
"Heading: " + Math.round(radToDeg(heading)) + "°",
"Speed: " + (speed * 3.6).toFixed(1) + " km/h",
"Delta: " + Math.round(deltaMean) + "ms"
].join("<br />");
document.getElementById("info").innerHTML = html;
});
geolocation.on("error", function () {
alert("geolocation error");
// FIXME we should remove the coordinates in positions
});
// convert radians to degrees
function radToDeg(rad) {
return rad * 360 / (Math.PI * 2);
}
// convert degrees to radians
function degToRad(deg) {
return deg * Math.PI * 2 / 360;
}
// modulo for negative values
function mod(n) {
return ((n % (2 * Math.PI)) + (2 * Math.PI)) % (2 * Math.PI);
}
function addPosition(position, heading, m, speed) {
var x = position[0];
var y = position[1];
var fCoords = positions.getCoordinates();
var previous = fCoords[fCoords.length - 1];
var prevHeading = previous && previous[2];
if (prevHeading) {
var headingDiff = heading - mod(prevHeading);
// force the rotation change to be less than 180°
if (Math.abs(headingDiff) > Math.PI) {
var sign = (headingDiff >= 0) ? 1 : -1;
headingDiff = -sign * (2 * Math.PI - Math.abs(headingDiff));
}
heading = prevHeading + headingDiff;
}
positions.appendCoordinate([x, y, heading, m]);
// only keep the 20 last coordinates
positions.setCoordinates(positions.getCoordinates().slice(-20));
// FIXME use speed instead
if (heading && speed) {
markerEl.src = "/OrchardLocal/Media/Default/Map/geolocation_marker.png"; //"data/geolocation_marker_heading.png";F:\DeleteMeThree\_Orchard-19x\src\Orchard.Web\Modules\Cns.OL\Contents/Images/geolocation_marker.png
} else {
//alert(markerEl.src); PETE: Not sure if this is std OL practice, but this is achieved by already having an element
//called "geolocation_marker" in the dom as an img, which this uses? Strange to me
markerEl.src = "/OrchardLocal/Media/Default/Map/geolocation_marker.png"; //I added img via media module - ridiculous?!
}
}
var previousM = 0;
// change center and rotation before render
map.beforeRender(function (map, frameState) {
if (frameState !== null) {
// use sampling period to get a smooth transition
var m = frameState.time - deltaMean * 1.5;
m = Math.max(m, previousM);
previousM = m;
// interpolate position along positions LineString
var c = positions.getCoordinateAtM(m, true);
var view = frameState.viewState;
if (c) {
view.center = getCenterWithHeading(c, -c[2], view.resolution);
view.rotation = -c[2];
marker.setPosition(c);
}
}
return true; // Force animation to continue
});
// recenters the view by putting the given coordinates at 3/4 from the top or
// the screen
function getCenterWithHeading(position, rotation, resolution) {
var size = map.getSize();
var height = size[1];
return [
position[0] - Math.sin(rotation) * height * resolution * 1 / 4,
position[1] + Math.cos(rotation) * height * resolution * 1 / 4
];
}
// postcompose callback
function render() {
map.render();
}
//EMP
//$("#geolocate").click(function () {
// alert("JQuery Running!");
//});
// geolocate device
var geolocateBtn = document.getElementById("geolocate");
geolocateBtn.addEventListener("click", function () {
geolocation.setTracking(true); // Start position tracking
map.on("postcompose", render);
map.render();
disableButtons();
}, false);
});
})
Considering that i'll have many more modules to attach in the future, what would be the best way to break-up this code using RequireJS for efficiency and coding functionality / maintenance.
Thanks ever so much for your guidance / thoughts, cheers WL
Every require module (defined using define) is supposed to return a function/object. The breakup shown in question doesn't, instead just splits the code. Think of some hypothetical module buckets and put each piece of code (or function) into a module. Then group the code into a require js module and return the module's interface.
Let me try to explain further with an example.
main.js
$(document).ready(function(){
$("#heyINeedMap").click(function(){
require(['map'],function(Map){
Map.render($target);
});
});
});
OR
$(document).ready(function(){
require(['map','geolocation'],function(Map,Geolocation){
window.App.start = true;
window.App.map = Map; //won't suggest, but you can do.
window.App.geolocation = Geolocation;
//do something.
$("#lastCoords").click(function(){
var coords = App.geolocation.getLastSavedCoords();
if(!!coords){
coords = App.geolocation.fetchCurrentCoords();
}
alert(coords);
});
});
});
map.js
define(['jquery'],function($){
var privateVariableAvailableToAllMapInstances = 'something';
var mapType = 'scatter';
return function(){
render: function(el){
//rendering logic goes here
},
doSomethingElse: function(){
privateVariable = 'some new value';
//other logic goes here
},
changeMapType: function(newType){
mapType = newType;
//...
}
}
});
geolocation.js
//Just assuming that it needs jquery & another module called navigation to work.
define(['jquery','navigation'], function($,Gnav){
return {
var coordinates = Gnav.lastSavedCoords;
fetchCurrentCoords: function(){
//coordinates = [79.12213, 172.12342]; //fetch from API/something
return coordinates;
},
getLastSavedCoords: function(){
return coordinates;
}
}
});
Hope this gives an idea on how to proceed.

how can I set markers with different colours depending on the markertype, and how to determinate their coordinates?, Im using openlayers.org library

I am starting using openlayers javascript library from openlayers.org.
I want to set dinamic markers with diferent colours for each device type(cam marker, server marker and so on), and I tried different ways to set this, but it doesn't work actually.
This is the map that Im developing: http://manotazsoluciones.com/map/.
Another problem that Im facing is when I set coordinates. For example: if I set [0,0] coordinates on a marker, when I use click event, the marker get another coordinates like
[30000,-7.081154551613622e-10].
this is the code that Im using to display the map on manotazsoluciones.com/map
<!DOCTYPE html>
<html>
<head>
<title>Manotaz Soluciones</title>
<script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.6.0/ol.css" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.6.0/ol.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row-fluid">
<div class="col-md-10 col-md-offset-1">
<div id="map" class="map">
<div id="popup">
<div id="popup-content"></div>
</div>
</div>
</div>
</div>
</div>
<script>
$( document ).ready(function() {
/**************** DRAG AND DROP EVENTS ****************/
/**
* Define a namespace for the application.
*/
window.app = {};
var app = window.app;
/**
* #constructor
* #extends {ol.interaction.Pointer}
*/
app.Drag = function() {
ol.interaction.Pointer.call(this, {
handleDownEvent: app.Drag.prototype.handleDownEvent,
handleDragEvent: app.Drag.prototype.handleDragEvent,
handleMoveEvent: app.Drag.prototype.handleMoveEvent,
handleUpEvent: app.Drag.prototype.handleUpEvent
});
/**
* #type {ol.Pixel}
* #private
*/
this.coordinate_ = null;
/**
* #type {string|undefined}
* #private
*/
this.cursor_ = 'pointer';
/**
* #type {ol.Feature}
* #private
*/
this.feature_ = null;
/**
* #type {string|undefined}
* #private
*/
this.previousCursor_ = undefined;
};
ol.inherits(app.Drag, ol.interaction.Pointer);
/**
* #param {ol.MapBrowserEvent} evt Map browser event.
* #return {boolean} `true` to start the drag sequence.
*/
app.Drag.prototype.handleDownEvent = function(evt) {
var map = evt.map;
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
return feature;
});
if (feature) {
this.coordinate_ = evt.coordinate;
this.feature_ = feature;
}
return !!feature;
};
/**
* #param {ol.MapBrowserEvent} evt Map browser event.
*/
app.Drag.prototype.handleDragEvent = function(evt) {
var map = evt.map;
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
return feature;
});
var deltaX = evt.coordinate[0] - this.coordinate_[0];
var deltaY = evt.coordinate[1] - this.coordinate_[1];
var geometry = /** #type {ol.geom.SimpleGeometry} */
(this.feature_.getGeometry());
geometry.translate(deltaX, deltaY);
this.coordinate_[0] = evt.coordinate[0];
this.coordinate_[1] = evt.coordinate[1];
};
/**
* #param {ol.MapBrowserEvent} evt Event.
*/
app.Drag.prototype.handleMoveEvent = function(evt) {
if (this.cursor_) {
var map = evt.map;
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
return feature;
});
var element = evt.map.getTargetElement();
if (feature) {
if (element.style.cursor != this.cursor_) {
this.previousCursor_ = element.style.cursor;
element.style.cursor = this.cursor_;
}
} else if (this.previousCursor_ !== undefined) {
element.style.cursor = this.previousCursor_;
this.previousCursor_ = undefined;
}
}
};
/**
* #param {ol.MapBrowserEvent} evt Map browser event.
* #return {boolean} `false` to stop the drag sequence.
*/
app.Drag.prototype.handleUpEvent = function(evt) {
this.coordinate_ = null;
this.feature_ = null;
return false;
};
/*************** DRAG AND DROP EVENTS END *************/
You can ignore the drag and drop events above, because it works fine
var devices = [
{
'id' : 1,
'device' : 'cam',
'brand' : 'dahua',
'coordinates' : [0,0]
},
{
'id' : 2,
'device' : 'cam',
'brand' : 'vivotes',
'coordinates' : [0,1]
},
{
'id' : 3,
'device' : 'cam',
'brand' : 'dahua',
'coordinates' : [0, 2]
},
{
'id' : 4,
'device' : 'rack',
'brand' : 'dahua',
'coordinates' : [0, 3]
}
];
the code above is just an example of the resource that I want to display
var circle = [];
for (var i = 0; i < devices.length; i++) {
circle[i] = new ol.Feature(
new ol.geom.Circle(
ol.proj.transform(devices[i].coordinates, 'EPSG:4326', 'EPSG:3857'),//usar latitud, longitud, coord sys
30000
)
);
}
on var circle Im saving the coordinates and size for each marker.
var styles = [
new ol.style.Style({
image: new ol.style.Icon({ //#type {olx.style.IconOptions}
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
opacity: 1,
population: 4000,
rainfall: 500
}),
fill: new ol.style.Fill({
color: [150, 150, 255, 1]
})
})
];
in styles im setting the color for all markers, but I want to change his values depending on the device type
// RENDER DEVICES
var objects = new ol.source.Vector({ features: circle })
var bullets = new ol.layer.Vector({
source : objects,
style: styles
});
above Im setting the markers and styles.
//layers-capaImagen, propiedades imagen principal
var extent = ol.proj.transformExtent([-50, 50, 50, -40], 'EPSG:4326', 'EPSG:3857');
var imgProjection = new ol.proj.Projection({
code: 'xkcd-image',
units: 'pixels',
extent: [0, 0, 1024, 968]
});
var capaImagen = new ol.layer.Image();
source = new ol.source.ImageStatic({
url: 'plano-vertical-knits.jpg',
imageExtent: extent,
projection: imgProjection,
center: ol.extent.getCenter(imgProjection.getExtent()),
extent: imgProjection.getExtent()
});
capaImagen.setSource(source);
//end propiedades imagen principal
//map features before render
var features = {
controls : ol.control.defaults({attribution : false}).extend([ new ol.control.FullScreen() ]),
interactions: ol.interaction.defaults().extend([new app.Drag()]),
layers : [capaImagen, bullets],
view: new ol.View({ center: [0, 0], zoom: 3 }),
target : 'map'
};
var map = new ol.Map(features);
above Im rendering the map with their features
// display popup on click
var pops = document.getElementById('popup');
var popupContent = document.getElementById('popup-content');
var popup = new ol.Overlay({/** #type {olx.OverlayOptions} */
element: pops,
autoPan: true,
stopEvent: false,
positioning: 'bottom-center',
autoPanAnimation: {
duration: 250
}
});
map.addOverlay(popup);
/* events ON map */
map.on('click', function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
return feature;
});
if (feature) {
var geometry = feature.getGeometry();
var firstCoord = geometry.getFirstCoordinate();
var lastCoord = geometry.getLastCoordinate();
popup.setPosition(firstCoord);
$(pops).popover({
'placement': 'top',
'html': true,
'content': feature.get('name')
});
//var latlong = ol.proj.transform([firstCoord, lastCoord], 'EPSG:4326', 'EPSG:3857');
popupContent.innerHTML = '<p>You clicked here:</p><p>'+lastCoord+'</p>';
$(pops).popover('show');
}
});
// change mouse cursor when over marker
map.on('pointermove', function(e) {
if (e.dragging) {
$('#popup-content').empty();
return;
}
});
/* events ON map END */
});
</script>
</body>
</html>
on click function Im trying to get the coordinates, and is where the coordinates shows me another values.
I extracted some of your codes and I added some modifications.
Style problem
Each feature needs to have a reference to the properties that you saved in array devices:
for (var i = 0; i < devices.length; i++) {
circle[i] = new ol.Feature(
{geometry: new ol.geom.Circle(
ol.proj.transform(devices[i].coordinates, ), 'EPSG:4326', 'EPSG:3857'),//usar latitud, longitud, coord sys
1
),
device: devices[i].device}
);
}
Additionally, it is necessary to set a different style for each desired property. Something like that should work:
var bullets = new ol.layer.Vector({
source: objects,
style: function (el, resolution) {//this is a style function
console.log(el, el.getProperties().device);
if (el.getProperties().device === 'cam')
return styles;
return styles2;
}
});
Problem with the coordinates
I think the problem in this case is due to the projection. You defined a custom projection (based on pixel) and applied it to the image. For the map view you don't define any projection (so it remains the default EPSG:3857). All the coordinates of the circle are converted from 'EPSG:4326' to 'EPSG:3857'. Therefore, the values that you see in the popup are in EPSG:3857 (not longitude and latitude). If you decide to continue to use EPSG:3857, you should also adapt the static image to this coordinates system via:
source = new ol.source.ImageStatic({
...............
imageExtent: .....
As you can find in the documentation imageExtent is the extent of the image in map coordinates. But in your code imageExtent is just the extent in pixel of the image.......

Categories