I saw an example of "Render geometries to a canvas":
var canvas = document.getElementById('canvas');
var vectorContext = ol.render.toContext(canvas.getContext('2d'), {size: [100, 100]});
var fill = new ol.style.Fill({color: 'blue'});
var stroke = new ol.style.Stroke({color: 'black'});
var style = new ol.style.Style({
fill: fill,
stroke: stroke,
image: new ol.style.Circle({
radius: 10,
fill: fill,
stroke: stroke
})
});
vectorContext.setStyle(style);
vectorContext.drawGeometry(new ol.geom.LineString([[10, 10], [90, 90]]));
vectorContext.drawGeometry(new ol.geom.Polygon([[[2, 2], [98, 2], [2, 98], [2, 2]]]));
vectorContext.drawGeometry(new ol.geom.Point([88, 88]));
But what to do with the geometry in the projection EPSG:4326 (or EPSG:3857)?
PS
I saw the question "How can we render an OpenLayers 3 feature to a canvas using a style but not using a map", but I don't understand what projection the code works with. And to clarify the author does not allow me to low reputation on stackoverflow.
The HTML canvas element is used to draw graphics, on the fly, via JavaScript.
If your drawing in a canvas there is no more projection anymore, you're in pixel coordinate. You'll have to transform your geometry in pixel.
See the map's getPixelFromCoordinate function if you're drawing in the map canvas.
#Catch I dug in a little. In that answer he uses canvas.width (in pixels) and ol.extent.getWidth(extent) /height. ol.Extent is only array of numbers and don't have any information about coordinate system. If you would use geographic coordinates it would work with them like they are carthesian. So it depends on what area you want to diplay, but I would recomend to tranform it to some projection and then use the advised approach - translate, scale, translate (scale is now supported in ol.geom).
I was able to draw a polygon on canvas:
var geoJson = '{"type":"Polygon","coordinates":[[[32.00592041015625,49.55951603052614],[31.97296142578125,49.51673910294474],[32.103424072265625,49.433752230525585],[32.224273681640625,49.346151509388676],[32.54974365234375,49.24718981286537],[32.81890869140625,49.09814978542761],[32.80517578125,49.0729662700941],[32.85736083984375,49.0657686340536],[32.87933349609375,49.08376076915357],[32.95623779296875,49.067568140816434],[32.98370361328125,49.09095579858044],[33.145751953125,49.081961848889364],[33.11828613281251,49.067568140816434],[33.123779296875,49.056770122686174],[33.24737548828125,49.063969062121345],[33.23089599609375,49.16284875720291],[33.12652587890625,49.18080571099239],[33.079833984375,49.256153800301064],[32.95898437500001,49.28841067865025],[32.87933349609375,49.3295971091282],[32.83538818359375,49.38863055043896],[32.8436279296875,49.42794681507826],[32.67608642578125,49.4672315972079],[32.67333984375001,49.4297331699307],[32.7447509765625,49.352413884497594],[32.66510009765625,49.34794084076262],[32.52227783203125,49.38460779401288],[32.31079101562501,49.513172668717914],[32.18856811523438,49.50247180563116],[32.18856811523438,49.50247180563116],[32.00592041015625,49.55951603052614]]]}';
var parser = new ol.format.GeoJSON();
var feature = parser.readFeature( geoJson );
var geom = feature.getGeometry();
var canvas = document.getElementById( 'canvas' );
var fill = new ol.style.Fill({ color: 'blue' });
var stroke = new ol.style.Stroke({ color: 'black' });
var style = new ol.style.Style({
fill : fill,
stroke: stroke,
image : new ol.style.Circle({
radius: 10,
fill : fill,
stroke: stroke
})
});
function render( height, width, canvas, geometry, style ) {
var vectorContext = ol.render.toContext(
canvas.getContext( '2d' ),
{ size: [width, height] }
);
var geom = geometry.clone(),
line = geom.getCoordinates()[0],
extent = ol.extent.boundingExtent( line );
var dxy = ol.extent.getCenter(extent),
sxy = [
width / ol.extent.getWidth(extent),
height / ol.extent.getHeight(extent)
];
var dx = dxy[0],
dy = dxy[1],
sx = sxy[0],
sy = sxy[1];
geom.translate( -dx, -dy );
geom.scale( Math.min(sx, sy), -Math.min(sx, sy) );
geom.translate( width / 2, height / 2 );
vectorContext.setStyle( style );
vectorContext.drawGeometry( geom );
}
geom.transform( 'EPSG:4326', 'EPSG:3857' );
render( 400, 400, canvas, geom, style );
<script src="https://openlayers.org/en/v4.6.5/build/ol.js"></script>
<canvas id="canvas" width="400" height="400" style="width: 400px; height: 400px;"></canvas>
Example:
OpenLayers rendering Geometry to Canvas
Related
I'm working on some custom drawing tools that will apply very specific styling most easily created and drawn directly to a canvas. I'm having a difficult time figuring out how to align coordinates to pixels correctly and anchor it to the map.
I also can't figure out how to draw anything on the ImageCanvas before -180 (or probably >360 for that matter) , short of directly modifying the canvas's transform in a pre/post render, but I couldn't quite get that working right either.
In the fiddle below, I have two line features added to a vector source. One is drawn from -180,0 to 0,0 and the other from -400,0, to -300,0 .
There's a red line displayed below the first line feature, which is drawn by the ImageCanvas, but it's in the wrong place, and zooming in/out, or translating/panning the map causes it to move about. The red line should cover the blue line.
There's no red line visible for the second line feature. It seems to have something to do with the transform, but I'm not sure.
I've tried adding the extent to the projection object, and that did seem to change things, but it wasn't clear what should go there in my case.
fiddle: https://jsfiddle.net/jroetman/fqsh2z46/51/
const extent = [-400, -85, 400, 85]
const textent = ol.proj.transformExtent(
extent,
"EPSG:4326",
"EPSG:3857"
)
const canvas = document.createElement("canvas");
const vsource = new ol.source.Vector({ wrapX: false });
const vlayer = new ol.layer.Vector({source: vsource})
const pixProj = new ol.proj.Projection({
code: "pixel-projection",
units: "pixels",
})
const points = [[extent[0],0], [extent[0] + 100,0]].map(p => ol.proj.transform(p, "EPSG:4326", "EPSG:3857"))
const points2 = [[-180,0], [0,0]].map(p => ol.proj.transform(p, "EPSG:4326", "EPSG:3857"))
vsource.addFeature(new ol.Feature(new ol.geom.LineString(points)))
vsource.addFeature(new ol.Feature(new ol.geom.LineString(points2)))
var ic = new ol.source.ImageCanvas({
ratio: 1,
canvasFunction: (extent, resolution, pixelRatio, size, projection) => {
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let f of vsource.getFeatures()) {
const coords = f.getGeometry().getCoordinates();
const pixel1 = this.map.getPixelFromCoordinate(coords[0]);
const pixel2 = this.map.getPixelFromCoordinate(coords[1]);
ctx.save();
ctx.beginPath();
ctx.moveTo(pixel1[0],pixel1[1]);
ctx.lineTo(pixel2[0],pixel2[1]);
ctx.closePath();
ctx.strokeStyle = "red";
ctx.stroke()
ctx.restore()
}
return canvas;
},
projection: pixProj
});
var imageLayer = new ol.layer.Image({
className: "annotate",
source: ic,
zIndex: 100
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vlayer,
imageLayer
],
view: new ol.View({
projection: "EPSG:3857",
minZoom: 2.75,
center: [-50000,-300000],
zoom: 6,
extent:textent
}),
});
Updated/working version created by John Robinson. Thank You!
https://jsfiddle.net/8anhd2vb/
Original posting
Ok, I worked around this issue, and simplified things a bit. I'd still be interested in understanding how to work with the ImageCanvas more, but for now....
I was able to draw directly on canvas of the vector layer itself in its "postrender"
using vlayer.getRenderer().context
https://jsfiddle.net/jroetman/fqsh2z46/95/
const extent = [-400, -85, 400, 85]
const textent = ol.proj.transformExtent(
extent,
"EPSG:4326",
"EPSG:3857"
)
const canvas = document.createElement("canvas");
const vsource = new ol.source.Vector({ wrapX: false });
const vlayer = new ol.layer.Vector({source: vsource})
const points = [[extent[0],0], [extent[0] + 100,0]].map(p => ol.proj.transform(p, "EPSG:4326", "EPSG:3857"))
const points2 = [[-50,0], [0,0]].map(p => ol.proj.transform(p, "EPSG:4326", "EPSG:3857"))
vsource.addFeature(new ol.Feature(new ol.geom.LineString(points)))
vsource.addFeature(new ol.Feature(new ol.geom.LineString(points2)))
vlayer.on('postrender', (e) => {
var ctx = vlayer.getRenderer().context;
for (let f of vsource.getFeatures()) {
const coords = f.getGeometry().getCoordinates();
const pixel1 = ol.render.getRenderPixel(e,map.getPixelFromCoordinate(coords[0]));
const pixel2 = ol.render.getRenderPixel(e,map.getPixelFromCoordinate(coords[1]));
ctx.save();
ctx.beginPath();
ctx.moveTo(pixel1[0],pixel1[1] );
ctx.lineTo(pixel2[0],pixel2[1]);
ctx.closePath();
ctx.strokeStyle = "red";
ctx.stroke()
ctx.restore()
}
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vlayer,
],
view: new ol.View({
projection: "EPSG:3857",
minZoom: 2.75,
center: ol.proj.transform([-10,-5],"EPSG:4326","EPSG:3857"),
zoom: 5,
extent:textent
}),
});
In three.js I draw a map using displacementMap option. Like this
// ADD CAMERA
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.up.set( 0, 0, 1 );
camera.position.set(GAME_CONTEXT.camera_init_position.x, GAME_CONTEXT.camera_init_position.y, GAME_CONTEXT.camera_init_position.z);
camera.lookAt(Utils.GetCameraLookAtDirection(GAME_CONTEXT, camera));
// ADD MAP
const loader = new THREE.TextureLoader();
const height = loader.load(GAME_CONTEXT.height_map_link);
const texture = loader.load(GAME_CONTEXT.texture_map_link);
const map_geometry = new THREE.PlaneBufferGeometry(GAME_CONTEXT.width, GAME_CONTEXT.height, 64, 64);
const map_material = new THREE.MeshStandardMaterial({
color:'orange',
//side: THREE.DoubleSide,
map:texture,
displacementMap: height,
displacementScale: GAME_CONTEXT.scale,
//alphaMap: alpha,
//transparent:true
});
const map_plane = new THREE.Mesh(map_geometry, map_material);
map_plane.position.x += GAME_CONTEXT.width/2;
map_plane.position.y += GAME_CONTEXT.height/2;
scene.add( map_plane );
Now if I have a (x,y,z) coordinate in the game world (the z is the surface height of the displacement map at (x,y)), how can I calculate the normalized vector representing the perpendicular vector to the plane?
Like in this image, if the yellow part is the surface of the terrain in the displacement map, then I want the vector in red as normalized too.
How can one calculate this?
In my app I wrote an ol.interaction.Draw code that allow me to draw a circle everytime I click on one map panel, and this circle work good for me because I can move, rotate and rescale proportionally it. This is my code:
map.addInteraction(new ol.interaction.Modify({
features: this.features,
deleteCondition: function (event) {
return ol.events.condition.shiftKeyOnly(event) && ol.events.condition.singleClick(event);
}
}));
this.draw = new ol.interaction.Draw({
features: this.features,
type: 'Circle',
draggable:true;
});
this.draw.on('drawstart', function () {
this.features.clear();
}, this);
this.map.addInteraction(this.draw);
But I would like to draw an image (e.g. with the source media/image/landscape.png), instead of one circle, but with the same features (drag and drop, rotate, rescale proportionally). How I could do it?
You would probably want to draw circles but style them using your png as an icon. Scaling would be based on the circle radius. Circle geometry doesn't include rotation but by using a geometryFunction in the interaction you could set a rotation and use that to rotate the icon (the angle needs to be adjusted depending on which edge or corner of the icon is used for the rotation).
var white = [255, 255, 255, 1];
var blue = [0, 153, 255, 1];
var width = 3;
styles = [
new ol.style.Style({
fill: new ol.style.Fill({
color: [255, 255, 255, 0.5]
})
}),
new ol.style.Style({
stroke: new ol.style.Stroke({
color: white,
width: width + 2
})
}),
new ol.style.Style({
stroke: new ol.style.Stroke({
color: blue,
width: width
})
}),
new ol.style.Style({
image: new ol.style.Circle({
radius: width * 2,
fill: new ol.style.Fill({
color: blue
}),
stroke: new ol.style.Stroke({
color: white,
width: width / 2
})
}),
zIndex: Infinity
})
];
var treeStyle = new ol.style.Style({
image: new ol.style.Icon({
src: 'https://www.freeiconspng.com/uploads/oak-tree-icon-png-17.png'
})
});
styleFunction = function(feature, resolution) {
if (feature.getGeometry().getCenter) {
treeStyle.setGeometry(new ol.geom.Point(feature.getGeometry().getCenter()));
treeStyle.getImage().setRotation(feature.getGeometry().get('rotation'));
treeStyle.getImage().setScale(feature.getGeometry().getRadius()/(150*resolution));
return treeStyle;
} else {
return styles;
}
}
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var source = new ol.source.Vector({wrapX: false});
var vector = new ol.layer.Vector({
source: source,
style: styleFunction
});
var map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View({
center: [-11000000, 4600000],
zoom: 4
})
});
var draw = new ol.interaction.Draw({
source: source,
type: 'Circle',
geometryFunction: function(coordinates, geometry) {
var center = coordinates[0];
var last = coordinates[1];
var dx = center[0] - last[0];
var dy = center[1] - last[1];
var radius = Math.sqrt(dx * dx + dy * dy);
var rotation = Math.PI - Math.atan2(dy, dx);
geometry = geometry || new ol.geom.Circle(center, radius);
geometry.setCenter(center);
geometry.setRadius(radius);
geometry.set('rotation', rotation);
return new ol.geom.Circle(center, radius);
},
style: styleFunction
});
map.addInteraction(draw);
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>
I am seeing other answers but it's in triangle shape. What I really need is to do a circle icon like in the picture below:
I tried customizing the "kendo.geometry.Rect" in this Dojo example by the Circle Geometry API. But I need help in understanding what I supposed to do.
You Circle Geometry only defines center and radius. You then need to use the drawing.Circle:
visual: function (e) {
// get color of current marker and label
var color = e.options.markers.background;
var labelColor = e.options.labels.color;
var rect = new kendo.geometry.Rect([0, 0], [100, 50]);
var layout = new kendo.drawing.Layout(rect, {
spacing: 5,
alignItems: "center"
});
// create a circle geometry centered at x=10, y=5, with a radius of 5
var CircGeometry = new kendo.geometry.Circle([10, 5], 5);
// draw the circle using the geometry and set the color (could have no stroke)
var MarkerCircle = new kendo.drawing.Circle(CircGeometry, {
stroke: { color: color, width: 1 },
fill: { color: color }
});
//Create the text label
var label = new kendo.drawing.Text(e.series.name, [0, 0], {
fill: {
color: labelColor
}
});
//Add circle and label to layout object
layout.append(MarkerCircle, label);
layout.reflow()
return layout;
}
I'm just starting out the KineticJS library and been playing about with it creating shapes etc.. However, I'm struggling to create a custom circle with my own image in it. I have tried using the fillPattern but it doesn't scale/centre correctly at all. Am I meant to use my own circle image or a rectangle image and then let KineticJS take care of things?
Just to give a bit of background: What I want is 3 balls bouncing in and then settling in place.
Any advice is welcome.
Sorted it... needed the offset values
var ball = new Image();
ball.src = 'ball2.jpg';
ball.height = 230;
ball.width = 230;
var stage = new Kinetic.Stage({
container: 'container',
width: 1000,
height: 1000
});
var circle = new Kinetic.Circle({
x: 300,
y: 300,
radius: 115,
fillPatternImage: ball,
fillPatternOffset :{
x: -115,
y: -115
}
});
var layer = new Kinetic.Layer();
// add the shape to the layer
layer.add(circle);
// add the layer to the stage
stage.add(layer);
ball.onload = function () {
stage.draw();
}