I'm developing a tool to rotate a static image layer and I'm currently facing an issue on mobile.
Static image layer must rotate around a specific point. I use precompose and postcompose hooks in order to alter canvas and make the rotation.
It works great on desktop but i don't understand what is going on on mobile (Android Chrome or FF).
jsFiddle : https://jsfiddle.net/wuty2m9v/7/
/* Vars */
const center = ol.proj.transform([1.44, 43.603], "EPSG:4326", "EPSG:3857")
const imgSize = [400, 267]
const imgExtent = [center[0], center[1], center[0] + imgSize[0], center[1] + imgSize[1]]
let rotation = 45
/* Init map */
const map = new ol.Map({
target: 'map',
view: new ol.View({
zoom: 16,
center,
}),
layers: [
// Map tiles layer
new ol.layer.Tile({
source: new ol.source.OSM()
}),
// Feature layer
new ol.layer.Vector({
source: new ol.source.Vector({
features: [
new ol.Feature({
geometry: new ol.geom.Point(center),
}),
],
}),
}),
],
})
/* Init image layer */
const imgLayer = new ol.layer.Image({
opacity: 0.7,
source: new ol.source.ImageStatic({
url: "https://pbs.twimg.com/profile_images/685220121598660608/2uZUdcS1.jpg",
imageExtent: imgExtent,
})
})
imgLayer.on("precompose", evt => {
const pixel = map.getPixelFromCoordinate(center)
const ctx = evt.context
ctx.save()
ctx.translate(pixel[0], pixel[1])
ctx.rotate(rotation * Math.PI / 180)
ctx.translate(-pixel[0], -pixel[1])
})
imgLayer.on("postcompose", evt => {
const ctx = evt.context
ctx.restore()
})
map.addLayer(imgLayer)
On desktop, image rotate correctly around the point. Drag the map : point and image follow the map.
On mobile, image doesn't rotate correctly around the point, it is made around the canvas origin, canvas translations don't look good. Drag the map to watch the chaos :)
You'll need to take into account the pixel ratio when doing transforms. The pixels you get from map.getPixelFromCoordinate are css pixels. To get canvas pixels, you need to multiply them by ol.has.DEVICE_PIXEL_RATIO:
ctx.translate(pixel[0] * ol.has.DEVICE_PIXEL_RATIO, pixel[1] * ol.has.DEVICE_PIXEL_RATIO);
and
ctx.translate(-pixel[0] * ol.has.DEVICE_PIXEL_RATIO, -pixel[1] * ol.has.DEVICE_PIXEL_RATIO);
See https://jsfiddle.net/wuty2m9v/8/ for an updated version of your JSFiddle.
If you are interesting in drawing images on a OL3 map, take a look at the ol.source.GeoImage I've published on GitHub: http://viglino.github.io/ol3-ext/examples/map.geoimage.html
With your example: https://jsfiddle.net/Viglino/cqL17a3y/
/* Init image layer */
const imgLayer = new ol.layer.Image({
opacity: 0.7,
source: new ol.source.GeoImage({
url: "https://pbs.twimg.com/profile_images/685220121598660608/2uZUdcS1.jpg",
imageCenter: center,
imageScale: [1,1],
imageRotate: rotation*Math.PI/180,
projection: 'EPSG:3857',
})
})
Regards
Related
I used OpenLayers in Angular2 to show a custom SVG-image as a background map. This works perfectly and I can zoom and scroll across my map.
Now I implemented Features (also svg) and put them all in a seperate Vector-Layer above the map. They are all shown but when I zoom, they get resized weird.
When I zoom out, the Features are getting bigger, when I zoom in the features are getting smaller. I would like to have them always in the same size, so that they are getting smaller when I zoom out (as if it would be static content on the map).
I tried with an extra style-function and experimented by recalculation the scale of the current style of the feature. But I did not get this to work.
So my question ist: How do I put another Layer with Features above an existing layer and when zooming out everything gets smaller?
Am I doing something wrong? Do I need Projections? I am quite new to Openlayers.
Thanks in advance!
A minimal code example, the SVG-Points are getting bigger and bigger when zooming out (same problem at the OL code example: Official Code Example: When Zooming out, the marker gets as big as a whole country or continent....
let center = ol.proj.transform([8.30368, 47.05243], 'EPSG:4326', 'EPSG:3857');
let style = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0, 0],
anchorXUnits: 'pixels',
anchorYUnits: 'pixels',
src: '/assets/images/test.svg',
imgSize: [50, 50],
size: [50, 50],
scale: 1.0
})
});
let iconFeature = new ol.Feature({
geometry: new ol.geom.Point(center)
});
iconFeature.setStyle((resolution) => {
return [iconStyle];
});
this.featureArray.push(iconFeature);
let vectorSource = new ol.source.Vector({
features: this.featureArray
});
let extent = [0, 0, 1024, 968];
let projection = new ol.proj.Projection({
code: 'xkcd-image',
units: 'pixels',
extent: extent
});
// load vector layer for dynamic elements
let vectorLayer = new ol.layer.Vector({
source: vectorSource,
projection: projection,
imageExtent: extent
});
this.layerArray.push(vectorLayer);
// basic setup
this.map = new ol.Map({
renderer: 'canvas',
layers: this.layerArray,
view: new ol.View({
center: center,
zoom: 16,
minZoom: 1,
maxZoom: 26,
})
});
You mean something like https://codepen.io/anon/pen/WOmqmN?&editors=1110?
That requires a style function as style, where you scale the icon according to the resolution:
function(feature, resolution) {
style.getImage().setScale(16000 / resolution);
}
16000 ist the resolution at which the icon size should be the icon image's original size.
Also note that the ol.layer.Vector instance is configured with
updateWhileAnimating: true,
updateWhileInteracting: true
to make sure the icon size is updated whenever the resolution changes.
currently i have a vector map displayed using a KML file as the vector source.
what i want to do is have an image underlay this vector map.
The map is an indoor floormap, the image is the exact same as the vector map only there is more details in it, text written on it etc. What i need is for the map image to underlay the vector map so that the walls of the vector map align perfectly with the walls of the image. This can happen because the KML was created by tracing on top of the images using QGIS.
so far ive been able to have the KML vector map and png image appear on the map, but they are not alligned with each other and are not the same size. This is what i need help with!
here some code for what i currently have:
create the map, no layers yet (maps selected from dropdown boxes)
var map = new ol.Map({
layers: [],
target: 'floormap',
interactions: ol.interaction.defaults({mouseWheelZoom:false}),
view: new ol.View({
center: [0, 0],
zoom: 19,
minZoom: 15,
maxZoom: 30
})
});
add selected map (KML) to map
map.removeLayer(vector);
vector = new ol.layer.Vector({
source: new ol.source.Vector({
url: MAPS_URL + maps[map_id],
format: new ol.format.KML()
})
});
map.addLayer(vector);
setMapExtent(vector);
now i tried to add the image, which works but its not alligned
// this part here i feel may be the problem,
// i just copied and pasted from an example om openlayers.org,
// i dont actually know much about the extent and how to match it to
// the vector map
var extent = [0,0,1024,684];
var projection = new ol.proj.Projection({
code: 'xkcd-image',
units: 'pixels',
extent: extent
});
image = new ol.layer.Image({
source: new ol.source.ImageStatic({
attributions: [
new ol.Attribution({
html: '© xkcd'
})
],
url: MAPS_URL + images[map_id],
projection: projection,
imageExtent: extent
})
});
map.addLayer(image);
the setMapExtent method
function setMapExtent(vectorMap) {
var vectorSource = vectorMap.getSource();
var listenerKey = vectorSource.on('change', function () {
if (vectorSource.getState() === 'ready') {
var extent = vectorSource.getExtent();
map.getView().fitExtent(extent, map.getSize());
vectorSource.unByKey(listenerKey);
}
});
}
at this point i have a vector map with an image sitting way above the map, and the image seems to be smaller too.
Can anyone help me with this issue?
*** Solution! ***
a working solution, although probably not the best way to do it, but it works none the less.
var map = new ol.Map({
layers: [],
target: 'floormap',
interactions: ol.interaction.defaults({mouseWheelZoom:false}),
view: new ol.View({
center: [0, 0],
zoom: 19,
minZoom: 15,
maxZoom: 30
})
});
add new map layer
map.removeLayer(vector);
vector = new ol.layer.Vector({
source: new ol.source.Vector({
url: MAPS_URL + maps[map_id],
format: new ol.format.KML()
})
});
map.addLayer(vector);
setMapExtent(vector);
// call image adding function pass in vector
// to get its extend
addImage(vector);
the addImage function
function addImage(vectorMap) {
var vectorSource = vectorMap.getSource();
// listen for one change on the vector to get the extent of it
// for use in setting the image extent. tried to use on.('load')
// but it didnt work
var listenerKey = vectorSource.once('change', function () {
var extent = vectorSource.getExtent();
var projection = new ol.proj.Projection({
code: 'xkcd-image',
units: 'pixels',
extent: extent
});
image = new ol.layer.Image({
source: new ol.source.ImageStatic({
attributions: [],
url: MAPS_URL + images[map_id],
projection: projection,
imageExtent: extent
})
});
// remove vector layer else they keep stacking up
map.removeLayer(vector);
// add image
map.addLayer(image);
// re-add vector only push so it goes above the image
map.getLayers().push(vector);
});
}
seems to work pretty well! can anyone help me with layer ordering?
Your static image must be georeferenced correctly with the view's projection.
Default view's projection is EPSG:3857 (Spherical Mercator)., the extent of this projection is [-20026376.39, -20048966.10, 20026376.39, 20048966.10]
In your code you specify a projection in pixels for your static layer. You need to use the view's projection, something like this :
// Here the extent of your layer in EPSG:3857 -> [minx, miy, max, mayy]
var extent = [-10000000, -10000000, 10000000, 10000000];
image = new ol.layer.Image({
source: new ol.source.ImageStatic({
attributions: [
new ol.Attribution({
html: '© xkcd'
})
],
url: MAPS_URL + images[map_id],
imageSize: [1024, 684], // Don't forget the image size here
imageExtent: extent
})
});
map.addLayer(image);
Update:
For layer ordering if you want your vector layer on top use push:
http://openlayers.org/en/v3.8.2/apidoc/ol.Collection.html#push
map.getLayers().push(vector)
I try to add on my map an image mask on the foreground, some kind of shadow frame.
The problem is that I've not been able to find a good solution to do so by trying:
precompose/postcompose overload (drawing the image with the canvas's ctx)
adding a layer
adding an overlay
adding a div over the map (events propagation issues and jQuery exceptions)
Has anyone faced this problem?
A solution is to bind on postcompose event.
Here is the JS code making it working:
var osm = new ol.layer.Tile({
source: new ol.source.OSM()
});
var map = new ol.Map({
layers: [osm],
target: 'map',
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}),
view: new ol.View({
center: [0, 0],
zoom: 5
})
});
var image = new Image();
var loaded = false;
image.src = 'http://safari.am/images/frame_shadow.png';
image.onload = function() {
loaded = true;
};
osm.on('postcompose', function(event) {
var ctx = event.context;
if(loaded)
ctx.drawImage(image, 0, 0);
ctx.restore();
});
You can find the full solution in the following JSFiddle
I am developing a solution that uses Openlayers 3 to display static images of text documents that have been converted from PDFs. Some of my documents are black text on a white background - the page I'm displaying openlayers on is also black, as is the map background - so the document is invisible.
My current solution is to draw a white filled polygon at the same extent as the image being loaded but behind the text - essentially providing a white background for the extent I'm interested in.
The problem is when I pan around, the polygon is constantly redrawing, causing some undesirable effects. See this jsFiddle for an example.
Is there an alternative way of setting the background colour of a layer or is there a setting that will prevent the polygon from flickering.
CSS
body{
background-color:black;
}
HTML
<div id="map" class="map"></div>
Javascript
var imgURL = 'http://i.stack.imgur.com/9C8Xu.png';
var extent = [0,0,488,198];
var projection = new ol.proj.Projection({
units: 'pixels',
extent: extent
});
//A polygon that will represent a white background
var polyFeature = new ol.Feature({
geometry: new ol.geom.Polygon([
[
[0, extent[1]],
[0, extent[3]],
[extent[2], extent[3]],
[extent[2], extent[1]]
]
])
});
//A base layer to hold the polygon feature
var baseLayer = new ol.layer.Image({
source: new ol.source.ImageVector({
source: new ol.source.Vector({
features: [polyFeature]
}),
style: new ol.style.Style({
fill: new ol.style.Fill({color: 'white'})
})
})
});
var docLayer = new ol.layer.Image({
source: new ol.source.ImageStatic({
url: imgURL,
imageExtent: extent
})});
// build the map
var map = new ol.Map({
layers:[baseLayer,docLayer],
target: 'map',
view: new ol.View({
projection: projection,
center: ol.extent.getCenter(extent),
zoom: 2.5
})
});
I'm trying to overlay an image over an OpenLayers 3.0 map by adding a point feature to the layer, and setting the icon to the image to load. How can I get it to scale with the map as it is being zoomed?
Or is there a better way to overlay an image atop a layer?
p=ol.proj.transform( [-78,40],'EPSG:4326','EPSG:3857')
var f=new ol.Feature({ geometry: new ol.geom.Point(p) });
var imgStyle=new ol.style.Style({
image: new ol.style.Icon(({
rotateWithView: false,
anchor: [.5,.5],
anchorXUnits: 'fraction', anchorYUnits: 'fraction',
opacity: 0.75,
src: 'http://www.viseyes.org/shiva/map.jpg'
}))
});
f.setStyle(imgStyle);
myLayerr.getSource().addFeature(f);
It looks like you are trying to use an image of a map as an overlay. Instead of using the image as an icon for a feature with a point geometry, you'd be better off using an image layer with a static image source. See the code below for an example (also http://jsfiddle.net/tschaub/orr6qfkc/).
var extent = ol.proj.transformExtent(
[-5.6342, 50.3331, 1.6607, 53.0559], 'EPSG:4326', 'EPSG:3857');
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.MapQuest({layer: 'osm'})
}),
new ol.layer.Image({
source: new ol.source.ImageStatic({
url: 'http://www.viseyes.org/shiva/map.jpg',
imageExtent: extent
})
})
],
view: new ol.View({
center: ol.extent.getCenter(extent),
zoom: 7
})
});
I've just guessed at the geographic extent of the image. It may also be that the image overlays better on a map with the view projection set to 'EPSG:4326'.
Note that if you want to use an icon to symbolize a point feature and you want it to rotate and scale with the map (as the title of this question implies), you need to do two things:
Set the rotateWithView option to true (the default of false means that the icon will not rotate when you rotate the map).
Give your vector layer a style function. This function will be called with your feature and the view resolution. You can then scale your icon using the resolution. The style function below should give you a rough idea of how this could work.
// resolution at which to display the
// icon at 1:1
var maxResolution = 10000;
function style(feature, resolution) {
var icon = new ol.style.Style({
image: new ol.style.Icon({
src: 'http://example.com/icon.png',
scale: maxResolution / resolution,
rotateWithView: true
}))
});
return [symbolizer];
}
Thanks to Tim's help, I was able to rotate image markers that scale with the view. Max
Here's the final code :
var p=ol.proj.transform( [-78,40],'EPSG:4326','EPSG:3857')
var f=new ol.Feature({ geometry: new ol.geom.Point(p) });
this.boxLayer.getSource().addFeature(f);
this.boxLayer.setStyle(function(feature, resolution) {
var styleArray = [ new ol.style.Style( {
image: new ol.style.Icon({
src: 'http://www.viseyes.org/shiva/map.jpg',
scale: maxResolution/resolution,
rotateWithView: true,
rotation: .5,
anchor: [.5,.5],
anchorXUnits: 'fraction', anchorYUnits: 'fraction',
opacity: 0.75
})
})];
return styleArray;
});