How can I use an SVG image as a map marker in OpenLayers-3? - svg

I am trying to create map "pin-drops" (ie. map markers) in OpenLayers-3 (OL3) using SVG images.
Currently, I am using PNG images as the pindrops that reference the ol.style.Icon source (“src”) property attribute just fine. However, this fails using an SVG image. Is there some other way to use an SVG in the same manner? Maybe by using a reference besides ol.style.Icon even? There is already a lot of built-in SVG in Open Layers so this should be possible, but I haven't found a way to get this working in OL3. Is there some other way to do this in OL3 that I should consider?
Please note: we already tried using an ol.Vector layer, however when the user zooms in/out, the size of the SVG image grows/shrinks which is an inadequate workaround.
OL3 (fails):
var createMapMarkerImage = function() {
return function(feature, resolution) {
var iconStyle = new ol.style.Style({
image: new ol.style.Icon( ({
src: 'img/map_pindrop.svg' // OL3 doesn’t like this, but accepts a .PNG just fine
}))
});
return [iconStyle];
};
};
Very similar functionality, is the below example I found online, is almost perfect if it weren’t for the fact that the example uses OpenLayers-2 (OL2) functionality which calls openlayers.js library (instead of OL3’s ol.js library). Sadly, swapping these javascript files out fails.
OL2 (works -but is the old OL library):
http://dev.openlayers.org/sandbox/camptocamp/tipi/examples/vector-symbols.html
Searching online for a solution to this seems to produce only other confused people searching for a solution.
Please help,
FreeBeer

Based on #ahocevar answer, you can use data URIs for SVG:
new ol.style.Style({
image: new ol.style.Icon({
anchor: [0, 0],
src: 'data:image/svg+xml;utf8,<svg>/* SVG DATA */</svg>'
})
});

Convert the SVG to Base 64 . This (Link) helped me.
copied the base 64 and used it as a string in javascript .
Eg : var svg = "convertedBase64";
Then
var icon = new ol.style.Icon({
src:'data:image/svg+xml;base64,'+svg ,
other props
});
And you are done, may be a few Kbs more than SVG but this did work perfect for me .

SVG icons work fine as long as the content-type of your SVG image file is image/svg+xml. Also note that no external references are supported inside the SVG. OpenLayers 3 simply uses the drawImage function of the 2d context. You can find more details on the requirements of SVG content here: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Drawing_DOM_objects_into_a_canvas.

I had the same issue, but not even serving the image with the proper mime type helped.
It boiled down to the SVG not defining width and height properly.
I added the width and height attributes to the <svg> tag, like:
<svg width="100px" height="100px" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0.75 0 30 45" xml:space="preserve">
After that I was able to use my svg just like any other image.

I also had issues to show the icon image, ahocevar answer helped me to
solve my problem but I had also to search for the php header, for SVG
In case you are or others who see this answer are using php to generate the SVG you have to use header function to identify the content-type
header('Content-type: image/svg+xml'); /* this line will do the magic */
echo '<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>
</svg>';

Building upon SoonDead's answer, I had to come up with a way to add the width and height to the svg without touching the source. Using angular, this is sort of what I did:
$http.get('path/to/image.svg').then(function (response) {
// create element
var svgEl = angular.element(response.data);
// set width and height
svgEl.attr('width', '50px');
svgEl.attr('height', '50px');
// base64 encode
var base64Svg = btoa(unescape(encodeURIComponent(svgEl[0].outerHTML)));
// create the style
var style = new ol.style.Style({
image: new ol.style.Icon({
src: 'data:image/svg+xml;base64,'+base64Svg,
imgSize: [50, 50],
size: [50, 50],
})
});
// apply the style
feature.setStyle(style);
});
It's a little verbose, but it seems to do the job.

Related

How to draw a polygon and save it to image using Node.js

I'm trying to create an image, draw polygon shapes on it and save it to a file in Node.js, but I'm having a hard time finding a library that can achieve that.
In addition, I would prefer a library that doesn't require the installations of third party programs.
Example of an image I'm trying to create:
Without having to use any library you can just write the SVG image.
const fs = require('fs')
const result = `
<svg
width="541"
height="271"
>
<rect
width="541"
height="271"
x="0"
y="0"
fill="#3d5ea1"
/>
<polygon
fill="white"
points="100,50 400,100 320,200 80,230"
/>
</svg>
`
fs.writeFile("./file.svg", result, (err) => {
if (err) {return console.err(err)}
console.log("The file was saved!")
});
This code will create a similar image like the one on your example In case you need a png or jpeg you can use any external library to transform the svg.

How do I convert SVG with image links to PNG/JPEG?

I'm using fabric.js to add and manipulate images on canvas element. I'd like to export the final result to an image file (JPEG/PNG), and have tried several approaches, without much luck.
The first was using the HTML5 canvas.toDataURL callback - didn't work (apparently this cannot be done with cross-origin images).
I then tried to export the canvas to SVG and use convert utility:
$ convert file.svg file.png
The SVG looks a bit like this:
<svg>
...
<image xlink:href="http://example.com/images/image.png"
style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;"
transform="translate(-160 -247.5)" width="320" height="495">
</image>
...
</svg>
That also didn't work, presumably because of the image links. I then tried to download the linked image and store it locally, replacing the http://...image.png link with /path/to/image.png. No luck.
Is there a way to make this happen? thanks.
UPDATE
I've since solved this by using fabric's node module (npm install fabric) and simply using fabric's JSON representation of the canvas (canvas.toJSON() in fabric) to generate an image (also uses canvas module - npm install canvas).
Here's a simple example on how to do this:
// import the fabric module
var fabric = require('fabric').fabric,
fs = require('fs');
// read the fabric json data as string
// (from file, db, etc)
var jsonString = readJSON(filepath);
// create canvas with specified dimensions
// (change to match your needs)
canvas = fabric.createCanvasForNode(100, 100);
// create output file to hold the png image
output = fs.createWriteStream("/tmp/image.png")
// load the json to the canvas and
// pipe the output to the png file
canvas.loadFromJSON(jsonString, function() {
var stream = canvas.createPNGStream();
stream.pipe(output)
});

draggable rect in svg drags only diagnolly on the screen

I tried making a small program where in I wrote code to drag a rectangle in svg. The program is quite simple. My problem is that the rectangle drags only diagnolly on the screen and not on the entire web page.
Here is my code..
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%" height="100%"
onload="Init( evt )" >
<script type="text/ecmascript">
//<![CDATA[
var svgDocument;
var svgRoot;
var newP;
var bmousedown=0;
var myCirc;
function Init(evt){
svgRoot= document.getElementsByTagName("svg")[0];
newP = svgRoot.createSVGPoint();
myCirc = document.getElementById("mycirc");
}
function getMouse(evt){
var position = svgRoot.createSVGPoint();
position.x = evt.clientX;
position.y = evt.clientY;
return position;
}
function onMouseDown(evt){
bmousedown=1;
newP=getMouse(evt);
doUpdate();
}
function onMouseMove(evt){
if(bmousedown){
newP=getMouse(evt);
doUpdate();
}
}
function onMouseUp(evt){
bmousedown=0;
}
function doUpdate(){
myCirc.setAttributeNS(null, "x", newP.x );
myCirc.setAttributeNS(null, "y", newP.y );
}
// ]]></script>
<rect id="mycirc" fill: #bbeeff" x="0" y="0" width="80" height="80"
pointer-events="visible"
onmousedown="onMouseDown(evt)"
onmousemove="onMouseMove(evt)"
onmouseup="onMouseUp(evt)"/>
</svg>
Please help me as I am unable to understand why does it not move on the entire screen.
i see the problem recreated on firefox, but it's not a single problem, your code is all over the place. i suggest going back to the drawing board before posting specific questions.
i'd also recommend a good reference on SVG or using a JS vector graphics library, as it would simplify things a little and will ease up the development a lot, if you're not interested in getting down to the nitty-gritty.
Here is the correct solution: http://jsfiddle.net/mihaifm/5GHJs/
The mistakes I think you made:
onload="Init( evt )" makes the variable evt global, a bad and useless thing to have. All the functions are also global, but this should be ok for this example.
the function calls for onmousedown etc. were using this global evt. (wrong). In order to get the correct event for each call you need to register some handlers.

How to use svg filters with raphael js?

i would like to know, which techniques should i use to apply svg filters to raphael paths?
I know that raphael tries to be as much cross browser with IE it can, but i was wondering if there was a way to add the filters using javascript.
I built a library to do this. You can do something like:
var paper = Raphael("test");
var circle = paper.circle(100, 100, 50, 50).attr({fill: "red", stroke: "black"});
circle.emboss();
Have a look at a fiddle: http://jsfiddle.net/chrismichaelscott/5vYwJ/
or the project page: http://chrismichaelscott.github.io/fraphael
It's quite possible to extend Raphaël to add svg filters, for blur look at raphael.blur.js. That can serve as a starting point for adding other filter effects. For a bit more info on filters (along with examples) see the SVG Primer.
One hacky way to use SVG filters with Raphael objects is the following technique. It creates Raphael rectangle and adds filter definition to the same SVG document. This of course doesn't work with older browsers which lack support for inline SVG. But this is not a big problem, because older browsers have also no SVG filter support.
Although this is not jQuery tagged question, for simplicity the code uses jQuery for DOM manipulations. The namespace problem is solutioned using dummy SVG element, which has the advantage that SVG elements can be created using text strings (instead of DOM methods). Let the browser do what browser can!
The working example is in http://jsbin.com/ilinan/1.
<html>
<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.0.0/raphael-min.js"></script>
<script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var p = Raphael("cont", 300, 200);
$(p.canvas).attr("id", "p");
var rect = p.rect(10, 10, 100, 100);
$(rect.node).attr("id", "rect");
$("#rect").attr("filter", "url(#innerbewel)");
$("#rect").attr("fill", "red");
var f = "<filter id='innerbewel' x0='-50%' y0='-50%' width='200%' height='200%'>\
<feGaussianBlur in='SourceAlpha' stdDeviation='2' result='blur'/>\
<feOffset dy='3' dx='3'/>\
<feComposite in2='SourceAlpha' operator='arithmetic'\
k2='-1' k3='1' result='hlDiff'/>\
<feFlood flood-color='white' flood-opacity='0.8'/>\
<feComposite in2='hlDiff' operator='in'/>\
<feComposite in2='SourceGraphic' operator='over' result='withGlow'/>\
\
<feOffset in='blur' dy='-3' dx='-3'/>\
<feComposite in2='SourceAlpha' operator='arithmetic'\
k2='-1' k3='1' result='shadowDiff'/>\
<feFlood flood-color='black' flood-opacity='0.8'/>\
<feComposite in2='shadowDiff' operator='in'/>\
<feComposite in2='withGlow' operator='over'/>\
</filter>";
// Create dummy svg with filter definition
$("body").append('<svg id="dummy" style="display:none"><defs>' + f + '</defs></svg>');
// Append filter definition to Raphael created svg
$("#p defs").append($("#dummy filter"));
// Remove dummy
$("#dummy").remove();
$("#rect").attr("fill", "orange");
});
</script>
</head>
<body>
<div id="cont"></div>
</body>

One SVG file, many SVG gradients inside

I’m making a set of buttons which use dynamic gradients. I’ve taken care of Firefox 3.6+ and WebKit by using their proprietary CSS extensions and all I need to do is support Opera, iOS and IE9 by using background-image: url("gradient.svg").
This is relatively easy, I made an SVG file, linked it and got it working. However, I’m making a set so I need at least 6 gradients. When I normally do it in images, I create a sprite for fast HTTP access. I’m not sure how to achieve this in SVG – can I use one file and access different parts of its XML by using #identifiers, like XBL does?
My current SVG:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="select-gradient" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="rgb(231,244,248)"/>
<stop offset="100%" stop-color="rgb(207,233,241)"/>
</linearGradient>
<style type="text/css">
rect {
fill: url(#select-gradient);
}
</style>
</defs>
<rect x="0" y="0" rx="6" ry="6" height="100%" width="100%"/>
</svg>
And then I have CSS:
.button-1 {
background-image: url("gradient-1.svg");
}
.button-2 {
background-image: url("gradient-2.svg");
}
I want to do something like this:
.button-1 {
background-image: url("gradient.svg#gradient1");
}
.button-2 {
background-image: url("gradient.svg#gradient2");
}
Is it even possible? Can you help me out? I really don’t wanna push 6 XML files when I can do it with one.
If you just want gradients for button backgrounds, most of this can be acheived in css. For the remaining browsers, ie6 + can user ms filters:
http://msdn.microsoft.com/en-us/library/ms532847.aspx
iOS uses webkit to render, so you can use -webkit vendor prefix. Unfortunately you will still need svg for opera, but this may make it easier (or just use a normal image sprite for opera's 1% of users)
in theory - according to SVG documentation #Params it is possible. You could use 2 params for setting up both colors, you could create multiple rects with different gradients, height set to 0 and then make only one 100% (like ?gradient2=100%)
What you could do is load your SVG file that contains all of the definitions first, and then load your other SVG files.
Using Firefox, jQuery SVG , and a minor shot of framework...
in your XHTML:
<div id="common_svg_defs"><!--ieb--></div>
<div id="first_thing"><!--ieb--></div>
<div id="second_thing"><!--ieb--></div>
in your JavaScript:
var do_stuff = function()
{
// load your common svg file with this goo.
$('#common_svg_defs').svg({
loadURL: 'path/filename.svg',
onLoad: function(svg, error) { run_test(svg, error);} });
}
var run_test = function(svg, error)
{
if (typeof(error) !== "undefined")
{
if (typeof(console.log) !== "undefined")
{
console.log(error);
}
}
else
{
// load your other svg files here, or just
// set a flag letting you know it's ready.
$('#first_thing').svg({
loadURL: 'path/anotherfilename.svg',
onLoad: function(svg, error) { somecallback(svg, error);} });
$('#second_thing').svg({
loadURL: 'path/anotherfilename.svg',
onLoad: function(svg, error) { somecallback(svg, error);} });
}
}
Because the id can be found in the documents scope, the SVG are capable of finding the IRI reference.
This allows you to define things once (that would not otherwise be defined in a css) and avoid id collisions.
Cheers,
Christopher Smithson

Resources