javascript - draw image with layers - javascript

Have I possibility to draw image in canvas, with layers? Particularly using fabric.js or literallycanvas.js ? Will be appreciate for any information or examples, ty

Layers are software abstractions. They define groups of items that are processed in a specific order (layered).
Anything can be layered, so drawing on the canvas it is easy to make layers.Group all the objects to be drawn in each layer together. Then render all objects in each layer together in the order defined by the layers.
A very simple layer example
var layers = []; // holds layers
function addLayer(order){ /// creates and adds a layer
var l;
layers.push(l = {
order : order,
items : [],
render : function(){
this.items.forEach(function(img){
ctx.drawImage(img.bitmap,img.x,img.y);
});
});
return l;
}
// some items for the layers
var img1 = {
bitmap : new Image("URL"),
x : 100,
y : 200,
}
var img2 = {
bitmap : new Image("URL"),
x : 300,
y : 200,
}
// create a layer order 1
var lay1 = AddLayer(1);
// add some items to lay1
lay1.items.push(img1);
lay1.items.push(img2);
// add a second layer order 0
var lay2 = AddLayer(0);
lay1.items.push(img3);
lay1.items.push(img4);
// sort layers by order
layers.sort(function(a,b){return a.order-b.order;});
// process each layer in the sorted order
layers.forEach(function(layer){
layer.render();
});

Related

How to customize HERE Maps Clusters

In my current Hybrid app I am in the process of switching from using Leaflet and its Clusterer plugin to using the HERE Maps v3 JavaScript API. The HERE Maps documentation can be rather dense at times for someone used to the clarity of Leaflet documentation. Nevertheless the process of using HERE is fairly straightforward. However, there is one Leaflet feature that I really miss:
In Leaflet when you add markers to a cluster it is possible to assign custom map pins to each marker. Further, you can quite easily customize the pin used to represent the cluster itself. In HERE the documentation indicates the following
var dataPoints = [];
//create an array for the clustered marker datapoints
dataPoints.push(new H.clustering.DataPoint(43.25539364396839, -79.07150530321474));
dataPoints.push(new H.clustering.DataPoint(43.255434408174246, -79.07175552759227));
dataPoints.push(new H.clustering.DataPoint(43.25557588373579, -79.07203209137799));
dataPoints.push(new H.clustering.DataPoint(43.25567419706804, -79.07218354297491));
//populate that array
var clusteredDataProvider = new H.clustering.Provider(dataPoints);
//create a cluster data provider and assign it the freshly created data points
var layer = new H.map.layer.ObjectLayer(clusteredDataProvider);
//create a new layer that uses this provider
map.addLayer(layer);
//inject this layer into the map
This works. However, it leaves me with three unanswered questions
How can I make the Cluster icon "explode" when tapped so the map zooms in automatically to the clustered map pins and shows each individual map pin with its assigned icon?
How do I assign individual PNG icon images to each of the "datapoints" above?
How to I customize the look and feel of the actual Cluster map pin itself?
Clusters and noise points are represented on the map with markers. Unless otherwise configured, the clustering provider uses the default bitmap markers theme with weight information to display clusters and noise points on the map. You can define your own custom theme and pass it to the provider as the theme property.
Read please this docu on https://developer.here.com/documentation/maps/3.1.22.0/dev_guide/topics/clustering.html
A custom theme is defined in an object that implements the interface H.clustering.ITheme as demonstrated by the code below.
/**
* Make clustering of markers with a custom theme
*
* Note that the maps clustering module https://js.api.here.com/v3/3.1/mapsjs-clustering.js
* must be loaded to use the Clustering
*
* #param {H.Map} map A HERE Map instance within the application
* #param {H.ui.UI} ui Default ui component
* #param {Function} getBubbleContent Function returning detailed information about photo
* #param {Object[]} data Raw data containing information about each photo
*/
function startClustering(map, ui, getBubbleContent, data) {
// First we need to create an array of DataPoint objects for the ClusterProvider
var dataPoints = data.map(function(item) {
// Note that we pass "null" as value for the "altitude"
// Last argument is a reference to the original data to associate with our DataPoint
// We will need it later on when handling events on the clusters/noise points for showing
// details of that point
return new H.clustering.DataPoint(item.latitude, item.longitude, null, item);
});
// Create a clustering provider with a custom theme
var clusteredDataProvider = new H.clustering.Provider(dataPoints, {
clusteringOptions: {
// Maximum radius of the neighborhood
eps: 64,
// minimum weight of points required to form a cluster
minWeight: 3
},
theme: CUSTOM_THEME
});
// Note that we attach the event listener to the cluster provider, and not to
// the individual markers
clusteredDataProvider.addEventListener('tap', onMarkerClick);
// Create a layer that will consume objects from our clustering provider
var layer = new H.map.layer.ObjectLayer(clusteredDataProvider);
// To make objects from clustering provider visible,
// we need to add our layer to the map
map.addLayer(layer);
}
// Custom clustering theme description object.
// Object should implement H.clustering.ITheme interface
var CUSTOM_THEME = {
getClusterPresentation: function(cluster) {
// Get random DataPoint from our cluster
var randomDataPoint = getRandomDataPoint(cluster),
// Get a reference to data object that DataPoint holds
data = randomDataPoint.getData();
// Create a marker from a random point in the cluster
var clusterMarker = new H.map.Marker(cluster.getPosition(), {
icon: new H.map.Icon(data.thumbnail, {
size: {w: 50, h: 50},
anchor: {x: 25, y: 25}
}),
// Set min/max zoom with values from the cluster,
// otherwise clusters will be shown at all zoom levels:
min: cluster.getMinZoom(),
max: cluster.getMaxZoom()
});
// Link data from the random point from the cluster to the marker,
// to make it accessible inside onMarkerClick
clusterMarker.setData(data);
return clusterMarker;
},
getNoisePresentation: function (noisePoint) {
// Get a reference to data object our noise points
var data = noisePoint.getData(),
// Create a marker for the noisePoint
noiseMarker = new H.map.Marker(noisePoint.getPosition(), {
// Use min zoom from a noise point
// to show it correctly at certain zoom levels:
min: noisePoint.getMinZoom(),
icon: new H.map.Icon(data.thumbnail, {
size: {w: 20, h: 20},
anchor: {x: 10, y: 10}
})
});
// Link a data from the point to the marker
// to make it accessible inside onMarkerClick
noiseMarker.setData(data);
return noiseMarker;
}
};
/**
* Boilerplate map initialization code starts below:
*/
// Helper function for getting a random point from a cluster object
function getRandomDataPoint(cluster) {
var dataPoints = [];
// Iterate through all points which fall into the cluster and store references to them
cluster.forEachDataPoint(dataPoints.push.bind(dataPoints));
// Randomly pick an index from [0, dataPoints.length) range
// Note how we use bitwise OR ("|") operator for that instead of Math.floor
return dataPoints[Math.random() * dataPoints.length | 0];
}
/**
* CLICK/TAP event handler for our markers. That marker can represent either a single photo or
* a cluster (group of photos)
* #param {H.mapevents.Event} e The event object
*/
function onMarkerClick(e) {
// Get position of the "clicked" marker
var position = e.target.getGeometry(),
// Get the data associated with that marker
data = e.target.getData(),
// Merge default template with the data and get HTML
bubbleContent = getBubbleContent(data),
bubble = onMarkerClick.bubble;
// For all markers create only one bubble, if not created yet
if (!bubble) {
bubble = new H.ui.InfoBubble(position, {
content: bubbleContent
});
ui.addBubble(bubble);
// Cache the bubble object
onMarkerClick.bubble = bubble;
} else {
// Reuse existing bubble object
bubble.setPosition(position);
bubble.setContent(bubbleContent);
bubble.open();
}
// Move map's center to a clicked marker
map.setCenter(position, true);
}
// 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
var map = new H.Map(document.getElementById('map'), defaultLayers.vector.normal.map, {
center: new H.geo.Point(50.426467222414374, 6.3054632497803595),
zoom: 6,
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));
// Step 4: create the default UI component, for displaying bubbles
var ui = H.ui.UI.createDefault(map, defaultLayers);
/**
* Merges given data with default bubble template and returns resulting HTML string
* #param {Object} data Data holding single picture information
*/
function getBubbleContent(data) {
return [
'<div class="bubble">',
'<a class="bubble-image" ',
'style="background-image: url(', data.fullurl, ')" ',
'href="', data.url, '" target="_blank">',
'</a>',
'<span>',
// Author info may be missing
data.author ? ['Photo by: ', '<a href="//commons.wikimedia.org/wiki/User:',
encodeURIComponent(data.author), '" target="_blank">',
data.author, '</a>'].join(''):'',
'<hr/>',
'<a class="bubble-footer" href="//commons.wikimedia.org/" target="_blank">',
'<img class="bubble-logo" src="data/wikimedia-logo.png" width="20" height="20" />',
'<span class="bubble-desc">',
'Photos provided by Wikimedia Commons are <br/>under the copyright of their owners.',
'</span>',
'</a>',
'</span>',
'</div>'
].join('');
}
// Step 5: request data that will be visualized on a map
startClustering(map, ui, getBubbleContent, photos);
This worked example try please on JsFiddle: https://jsfiddle.net/gh/get/jquery/2.1.0/heremaps/maps-api-for-javascript-examples/tree/master/custom-cluster-theme
The data for this example loaded from https://heremaps.github.io/maps-api-for-javascript-examples/custom-cluster-theme/data/photos.js
How the Cluster icon to "explode" when tapped you can see sources of this example https://tcs.ext.here.com/examples/v3.1/cluster_marker_spider . Note please on loaded additional js module "ClusterMarkerSpider-..."
How zoom-in to "exploded" of the clustered map pins - note please on "forEachDataPoint" (in Spider example) there are created all Markers which you can simple push to some H.map.Group and after zoom-in to this group by its bounding box.

Leaflet: showing dynamic polylines together on a map using layers

I'm working on a task of showing all polylines(of different colours) on my map using leaflet. I'm getting all my dynamic polylines' latitudes and longitudes with their colours but when I merge it using layers, it just takes the last polyline and shows it.
I think I'm making some mistake in layering it. Can someone recommend the correct way to layer polylines in leaflets?
Here's a sample of the code where this is happening -
let newColour = this.returnMapColor(moment(startDate).day());
var layerGroups = {}
console.log("colour", newColour, startDate );
let range = this.props.history.filter((v) => { return moment(v.time).format("YYYY-MM-DD") == moment(startDate).format("YYYY-MM-DD") });
startDate = moment(startDate).add(1, 'days');
range.map((row)=> {
positions.push([row.latitude,row.longitude,row.sp] )
});
if(this.props.map){
const leafletMap = this.props.map.leafletElement;
this.hotlineLayer = L.layerGroup(L.polyline(positions, {color: newColour})).addTo(leafletMap);
}
++i;
A polyline requires at least two sets of lat, longs. Could you point out where in your code are you forming polylines?
In your code, the range.map the operation will only lead to creating an array of positions. And hence, your code is rendering a single line.
If you intend to create multiple polylines and render them using LayerGroup, I would suggest something in the lines of:
var latlngs1 = [
[45.51, -122.68,0],
[37.77, -122.43,1],
[34.04, -118.2,2]
];
var latlngs2 = [
[34.04, -118.2,2],
[32.08, -110.5,2]
];
const polyline1 = L.polyline(latlngs1, {color: 'red'})
var polyline2 = L.polyline(latlngs2, {color: 'blue'})
var hotlineLayer = L.layerGroup([polyline1, polyline2]).addTo(map);
Example Code: https://jsfiddle.net/idhruvs/n75omjbd/32/

How to insert node into polyline link in gojs, preserving positions of points in links either side of the new node

I asked a question on StackOverflow (Seeking Javascript library for displaying and editing networks of nodes and edges) and was pointed at the gojs splice sample.
This has got me a long way, so thanks for the answer, but I have run into a brick wall trying to get the behaviour I want.
The app I am trying to create is to edit the borders on a map:
node = place where three or borders meet
link = segment of border between two nodes.
Hence, the nodes and links are unlabelled (nodes are just small circles, links are just polylines).
I have attempted to adapt the splice sample (https://gojs.net/extras/splicing.html) appropriately. The key features I need over and above what the sample does are:
choosing exactly where to position the new node on the link between the existing ones
preserving the shape of the polylines.
(The existing sample puts the new node equidistant between the existing ones and uses straight links.)
The user experience I have tried to create is this: first, you select the link, so it gets its usual adornments; then you shift-click on one of the adornments at a point on the polyline and that point becomes the new node.
I have sought to do this by overriding methods of the LinkReshapingTool using the extension mechanism described at https://gojs.net/latest/intro/extensions.html (rather than creating a subclass).
Whatever I have tried, though, I can't get the polylines to stay. By inspecting the diagram data model in the Chrome DevTools debugger after my code has run, it appears that it is correct (i.e. I can see the correct array of points in the links). However, when I then allow execution to continue the links do not display as expected (they are straight), and if I subsequently look at the data model then the multiple points have disappeared and each link just has a start and end.
I have tried various things, without success, for example:
deferring the splicing till after the tool has completed
passing the points into the modified links in different ways (array v list v string)
putting the processing into different overridden methods.
My current code is below. Please excuse crass stylistic faux pas - I am not an experienced JavaScript programmer.
<!DOCTYPE html> <!-- HTML5 document type -->
<!--
Adapted from splicing example from gojs.net
-->
<html>
<head>
<!-- use go-debug.js when developing and go.js when deploying -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/1.8.28/go-debug.js"></script>
</head>
<body>
<div id="myDiagramDiv"
style="width:400px; height:300px; background-color: #DAE4E4;"></div>
<script>
var $ = go.GraphObject.make;
// state variables to remember what to do after reshaping tool aborted
var doSpliceNode = false;
var doSpliceIntoLink = null;
var doSplicePoint = null;
var doSpliceIndex = -1;
// diagram
var myDiagram = $(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true
});
var tool = myDiagram.toolManager.linkReshapingTool;
// Override doMouseDown on linkreshapingtool. If user clicks on a handle with SHIFT pressed, want to insert
// a new node at that point rather than use the default behaviour to (further) reshape the link, and moreover
// want to retain the points in the link. I.e. turn one of the points in the link into a new node.
// (Existing gojs splicing example does not do this: it just puts a new node at the midpoint of the existing
// link, with no regard to the points along the link.)
tool.doMouseDown = function() {
console.log("mousedown at (" + this.Fp.M + "," + this.Fp.N + ")");
console.log(" on link from " + this.adornedLink.fromNode + " to " + this.adornedLink.toNode);
console.log(" with shift pressed? " + myDiagram.lastInput.shift);
var spliced = false;
if (myDiagram.lastInput.shift)
{
// work out which of the points on the link was clicked
var link = this.adornedLink;
var numpts = link.pointsCount;
var i;
var x = this.Fp.M; // ##TODO - by inspection in debugger this contains the X coord, but what's documented place to get this?
var y = this.Fp.N; // ##TODO - ditto for Y coord
for (i = 1; !spliced && (i < numpts - 1); i++)
{
if ((link.getPoint(i).x == x) && (link.getPoint(i).y == y))
{
console.log(" .. at point " + i);
// Store off what to do. (This used to be done inline - deferred to after as one of the things
// to try to make it work.)
doSpliceNode = true;
doSpliceIntoLink = link;
doSplicePoint = new go.Point(x, y);
doSpliceIndex = i;
spliced = true;
}
}
}
//if (!doSpliceNode)
{
console.log(".. call base class doMouseDown");
go.LinkReshapingTool.prototype.doMouseDown.call(tool);
}
}
// Override doMouseUp as well. If we had decided during mousedown to do the splice, then stop the tool now.
tool.doMouseUp = function()
{
// First call base class
go.LinkReshapingTool.prototype.doMouseUp.call(tool);
if (doSpliceNode)
{
// Doing splice - stop tool
console.log("STOP TOOL");
this.stopTool();
this.doDeactivate();
}
}
// Finally, override doStop to actually do the splice
tool.doStop = function() {
console.log("doStop");
// First call base class
go.LinkReshapingTool.prototype.doStop.call(tool);
if (doSpliceNode)
{
// now splice the node
console.log("splice node");
spliceNewNodeIntoLink2(doSpliceIntoLink, doSplicePoint, doSpliceIndex); // ##TODO make it respect points in existing link before and after
}
// Reset everything
doSpliceNode = false;
doSpliceIntoLink = null;
doSplicePoint = null;
doSpliceIndex = -1;
}
// Debug variable for inspecting later - not functional
var debugLastLink = null;
// Model, node and links for this application. Based heavily on https://gojs.net/temp/splicing.html and adapted as needed.
var myModel = $(go.GraphLinksModel);
myDiagram.nodeTemplate = $(go.Node,
"Auto",
new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape, "Circle", { width: 6, height: 6, strokeWidth: 0 }));
myDiagram.linkTemplate =
$(go.Link,
{
relinkableFrom: true, relinkableTo: true,
reshapable: true, resegmentable: true,
/* selectionAdornmentTemplate: ## COMMENT OUT - NOT NEEDED
$(go.Adornment,
$(go.Shape, { isPanelMain: true, stroke: "dodgerblue", strokeWidth: 2 }),
$(go.Shape, "PlusLine",
{
isActionable: true, // so that click works in an Adornment
width: 16, height: 16, stroke: "green", strokeWidth: 4, background: "transparent",
segmentOffset: new go.Point(8, 0),
click: function(e, shape) {
alert(e);
var link = shape.part.adornedPart;
var p0 = link.getPoint(0);
var p1 = link.getPoint(link.pointsCount - 1);
var pt = new go.Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
// ##TODO - instead, find the position where the mouse was clicked and place the node there
// ... need to work out which segment of polyline this was in so as to calculate new lines
// ##TODO - handle drag of node so that it just affects segments of lines immediately into it, rather than
// blatting over top of them
// ##TODO - what is object e and its components
spliceNewNodeIntoLink(link, pt);
},
cursor: "pointer"
})
), */
toShortLength: 1
},
new go.Binding("points").makeTwoWay(), // Use the points information from the linkDataArray initializer
$(go.Shape, { strokeWidth: 2 })
);
/* function spliceNewNodeIntoLink(link, pt) { // ## original version no longer called
link.diagram.commit(function(diag) {
var tokey = link.toNode.key;
// add a new node
var newnodedata = { text: "on link", location: go.Point.stringify(pt) };
diag.model.addNodeData(newnodedata);
// and splice it in by changing the existing link to refer to the new node
diag.model.setToKeyForLinkData(link.data, newnodedata.key);
// and by adding a new link from the new node to the original "toNode"
diag.model.addLinkData({ from: newnodedata.key, to: tokey });
// optional: select the new node
diag.select(diag.findNodeForData(newnodedata));
}, "spliced in node on a link");
} */
// Utility function used in one attempt to get this to work. Initializers in nodeDataArray do it via an array of numbers,
// so try that here.
function toArray(nodelist)
{
var returnarray = new Array();
var i;
for (i = 0; i < nodelist.size; i++)
{
var pt = nodelist.elt(i);
returnarray.push(pt.x);
returnarray.push(pt.y);
}
return returnarray;
}
// Function to splice the new node into the link. Parameters are
// - link: the link to splice into
// - pt: the point within the link to turn into a node
// - index: index into existing polyline of that point
function spliceNewNodeIntoLink2(link, pt, index) {
link.diagram.commit(function(diag) {
var oldlinkpointslist = link.points;
var link1pointslist = new go.List(go.Point);
var link2pointslist = new go.List(go.Point);
var i;
// Create new points list, from "from" node to new node to be added
for (i = 0; i <= index; i++)
{
var point = new go.Point(link.getPoint(i).x, link.getPoint(i).y);
link1pointslist.add(point);
}
console.log(link1pointslist);
// .. and from new node to "to" node
for (i = index; i < link.pointsCount; i++)
{
var point = new go.Point(link.getPoint(i).x, link.getPoint(i).y);
link2pointslist.add(point);
}
console.log(link2pointslist);
var tokey = link.toNode.key;
// add a new node
var newnodedata = { text: "on link", location: go.Point.stringify(pt) };
diag.model.addNodeData(newnodedata);
// and splice it in by changing the existing link to refer to the new node
diag.model.setToKeyForLinkData(link.data, newnodedata.key);
// ** NEW CODE
// Code this was based on re-used the existing link, re-purposing it to go from "from" node
// to new node, so do the same, but give it a new points list.
link.points = link1pointslist; // ##TODO find out why this doesn't work
// ... actually it does, but something ditches the points later ...
// so maybe I need to move this code to after the tool has really finished operating
// by saving off the info and calling it in an override of the last tool method that
// gets called (perhaps not - did this and it didn't work)
debugLastLink = link; // ##TEMP
// and by adding a new link from the new node to the original "toNode"
// ** UPDATED to include the second new point list
diag.model.addLinkData({ from: newnodedata.key, to: tokey, points: toArray(link2pointslist) });
// optional: select the new node
diag.select(diag.findNodeForData(newnodedata));
}, "spliced in node on a link");
}
// not called at present
function maySpliceOutNode(node) {
return node.findLinksInto().count === 1 &&
node.findLinksOutOf().count === 1 &&
node.findLinksInto().first() !== node.findLinksOutOf().first();
}
// not called at present
function spliceNodeOutFromLinkChain(node) {
if (maySpliceOutNode(node)) {
node.diagram.commit(function(diag) {
var inlink = node.findLinksInto().first();
var outlink = node.findLinksOutOf().first();
// reconnect the existing incoming link
inlink.toNode = outlink.toNode;
// remove the node and the outgoing link
diag.removeParts([node, outlink], false);
// optional: select the original link
diag.select(inlink);
}, "spliced out node from chain of links");
}
}
// Initialize modeldi
myModel.nodeDataArray = [
{ key: "1" , "location": "30 30" },
{ key: "2" , "location": "130 30" },
{ key: "3" , "location": "30 130" }
];
myModel.linkDataArray = [
{ from: "1", to: "2", "points": [ 30,30, 70,20, 100,40, 130,30 ] },
{ from: "2", to: "3", "points": [ 130,30, 100,80, 70,90, 30,130 ] },
{ from: "3", to: "1", "points": [ 30,130, 20,100, 40,70, 30,30 ] }
];
myDiagram.model = myModel;
</script>
</body>
</html>
Some suggestions:
Call Link.findClosestSegment to find the segment where the user clicked to insert a node.
Don't splice in the new node in an override of Tool.doStop, because that will be called even if the user hit the Escape key to cancel the tool's operation. Do it in either doMouseDown or doMouseUp, depending on the behavior that you want. But doStop is a reasonable time to clean up the tool's state.
I think it should work if you add the new Node and a new Link, connect them together properly, make sure the Node is at the right location, and only then set Link.points explicitly. The TwoWay Binding on Link.points will save the points to the model.
The problem that you are encountering is that when you create a new Node it takes time to be measured and arranged and positioned. Any one of those activities will invalidate the routes of all connected links. And obviously connecting a link with a node will invalidate that link's route. So you have to make sure everything is done in the right order.

Trouble in writing a small library in JavaScript

I'd been trying to write a small library in Javascript mainly for Canvas drawImage() method.
The main purpose of the library is to pass array values instead of passing single values such as :
// srcPos=[0,0] , size=[90,90], dstPos=[50,50]
function draw_image(context, image, srcPos, size, dstPos, size) {
context.drawImage(image, srcPos[0], srcPos[1], size[0], size[1], dstPos[0], dstPos[1], size[0], size[1]);
}
but when i called this function jzz like this, I'm getting Uncaught ReferenceError :
var canvas = document.getElementById("display"),
frame = canvas.getContext("2d");
var shipInfo = { center:[45, 45], size:[90, 90], radius: 35, angle:45 },
shipImage = new Image(),
pos = [40, 70];
shipImage.src = "ship.png";
function draw() {
draw_image(frame, shipImage, shipInfo.size, pos, shipInfo.size);
}
window.onload = function() {
draw();
}
And Is it possible to implement a method overriding the default drawImage() like this:
frame.draw_image(shipImage, srcPos, shipInfo.size, dstPos, shipInfo.size);
If you want to add a function to the 2d context, javascript makes this easy thanks to the prototype inheritance : You can inject the Context2D object to add or change its function as you wish.
You might want to look at a few addings i made to the context in a small canvas lib i made here : https://github.com/gamealchemist/CanvasLib
Some will tell that injecting is evil, but unless you're on a huge boat i would just say : If you use some graphic library, respect the semantic of existing functions and everything should be fine. If you don't use libs : do whatever it takes !
So, to answer more specifically to your question, your shorter drawImage would give :
CanvasRenderingContext2D.prototype.draw_image = function ( image,
srcPos, size,
dstPos, size) {
this.drawImage(image, srcPos[0], srcPos[1], size[0], size[1],
dstPos[0], dstPos[1], size[0], size[1]);
};
Then you can use the new function on all your contexts :
var canvas = document.getElementById("display"),
frame = canvas.getContext("2d");
frame.draw_image( ... ) ;
Notice that you could use 'rect' objects, which would be arrays with 4 elements, x, y, w, h, and lead to an even shorter syntax.
Edit : i see in your lib that you want to rotate your rect.
First thing is that you don't want to reset the transform. Just save it then restore it.
I would try something closer to this :
var x = dstPos[0],
y = dstPos[1],
halfWidth = dstSize[0]*0.5, // !! not src use >>1 if you know it's an int.
halfHeight = dstSize[1]*0.5, // !! not src ...
angleInRads = angle * Math.PI / 180;
this.save();
this.translate(x+halfWidth,y+halfHeight);
this.rotate(angleInRads);
this.drawImage(image
, center[0], center[1], srcSize[0], srcSize[1]
, -halfWidth, -halfHeight, dstSize[0],dstSize[1]);
this.restore();
Your small image library would fit well inside a javascript object.
A Demo: http://jsfiddle.net/m1erickson/7pZJw/
A javascript object can hold information about your image:
the image itself
the image size (can be automatically calculated for you)
the image centerpoint (can be automatically calculated for you)
Example:
// create a new object
// fill it with the info about the image
var object={
image:shipImage,
width:shipImage.width,
height:shipImage.height,
centerOffsetX:shipImage.width/2,
centerOffsetY:shipImage.height/2,
radius:35,
angle:45,
};
A javascript object can also hold functions that draws the image (as you've done in your code)
Example:
// when you call object.draw the image will be drawn by this function
// which is added to the object itself
draw:function(context,atX,atY,newWidth,newHeight){
context.drawImage(
this.image,
0,0,this.width,this.height,
atX,atY,newWidth,newHeight);
},
A function to create your small image library inside a javascript object might look like this:
function createImageObject(image,radius,angle){
// create a new object
// fill it with the info about the image
var object={
image:image,
width:image.width,
height:image.height,
centerOffsetX:image.width/2,
centerOffsetY:image.height/2,
radius:radius,
angle:angle,
draw:function(context,atX,atY,newWidth,newHeight){
context.drawImage(
this.image,
0,0,this.width,this.height,
atX,atY,newWidth,newHeight);
},
};
return(object);
}
And you can use your ship object library like this:
// create a new ship object
var shipObject=createImageObject(img,35,45);
// draw the ship image using the ship object
// draw at 20,20 with size 75,75
shipObject.draw(frame,20,20,75,75);
BTW, I see you're using the version of drawImage that will scale/clip the source image.
If you just want to draw the full image at its original size you can do this shortcut:
// draw the image full-sized at x,y
context.drawImage(image,x,y);

How to display markers (huge numbers) on a map which has been logically divided into segments?

What i have done so far:
i'm developing an application where i have to display more than(50K) points/Markers on the Navteq map divided into different segments.
for example: if i have 50K points i will divide all points into different segments.
if i divide 50K points into 50 segments each segment would have 1000 points (may not be 50 segments , it may depend).
right now it is working but it takes long time and hangs to render all the points on the MAP.so that i would like to perform segmentation displaying to display only few points with clustering.
so that i can get an idea of how the segment will look like.
but the problem here is i should only perform the clustering based on the segments.otherwise points from different segments willbe mixed together and displayed
as single unit and that conveys the wrong information to the user.
so here my question is: is it possible to perform the clustering based on the segment. so that only points from same segment will be clustered.
Note: if this is not possible, i would like to use Latest version of here-maps 2.5.3 (Asynchronous) may reduce some time while loading, so that i would like to use indexing functionality also while rendering the points
to improve the rendering time using nokia.maps.clustering.Index class.
i studied that indexing would reduce the time while rendering the points/markers on map. does it help in my case? could anybody please suggest how to perform indexing ?
This is the code with which i'm displaying points on map:
function displayAllLightPoints(arrLightPointCoordinats, totalLightPoints,
selectedSegmentId, totalSegmentsCount,segmentColorcode)
{
var MyTheme1 = function () {
};
segmentColorcode = segmentColorcode.substring(2,segmentColorcode.length-1);
MyTheme1.prototype.getNoisePresentation = function (dataPoint) {
var markerLightPoint = new nokia.maps.map.Marker(dataPoint, {
icon: new nokia.maps.gfx.BitmapImage("..//Images//Lightpoint//" +
segmentColorcode + ".png"),
anchor: {
x: 12,
y: 12
}
});
return markerLightPoint;
};
MyTheme1.prototype.getClusterPresentation = function (data) {
var markerLightPoint = new
nokia.maps.map.StandardMarker(data.getBounds().getCenter(), {
icon: new nokia.maps.gfx.BitmapImage("..//Images//
Segment/" + segmentColorcode + ".png", null, 66, 65),
text: data.getSize(),
zIndex: 2,
anchor: {
x: 12,
y: 12
}
});
return markerLightPoint;
};
var ClusterProvider = nokia.maps.clustering.ClusterProvider,
theme = new MyTheme1(),
clusterProvider = new ClusterProvider(map, {
eps: 0.00000000001,
minPts: 1000000,
strategy: nokia.maps.clustering.ClusterProvider.
STRATEGY_DENSITY_BASED,
theme: theme,
dataPoints: []
});
var lightpointsDataSet1 = new Array();
for (var i = 0; i < totalLightPoints; i++) {
lightpointsDataSet1[i] = { latitude: arrLightPointCoordinats[i][0],
longitude: arrLightPointCoordinats[i][1], title:
'LightPoint ' + (i + 1) };
}
clusterProvider.addAll(lightpointsDataSet1);
clusterProvider.cluster();
}
To deal with a very large (50K+) data set , I would do all the heavy number crunching server side and send over a new JSON response whenever the map is updated. Something like the HTML page described here
The key section of the code is the ZoomObserver:
var zoomObserver = function (obj, key, newValue, oldValue) {
zoom = newValue;
if (zoom < 7)
{ zoom = 7;}
if (zoom > 16)
{ zoom = 16;}
// Define the XML filename to read that contains the marker data
placeMarkersOnMaps('http://api.maps.nokia.com/downloads/java-me/cluster/'+ zoom + '.xml'
+ '?lat1=' + map.getViewBounds().topLeft.latitude
+ '&lng1='+ map.getViewBounds().topLeft.longitude
+ '&lat2='+ map.getViewBounds().bottomRight.latitude
+ '&lng2='+ map.getViewBounds().bottomRight.longitude);
};
map.addObserver("zoomLevel", zoomObserver );
Where the REST service returns a "well-known" data format which can be used to add markers and clusters to the map.
Now assuming you have two massive data sets you could make two requests to different endpoints, or somehow distinguish which cluster of data belongs to which so that you would just be returning information of the form:
{latitude':51.761,'longitude':14.33128,'value':102091},
i.e. using the DataPoint standard (which means you could use a heat map as well.
Of course, what I'm not showing here is the back-end functionality to cluster in the first place - but this leaves the client (and the API) to do what it does best displaying data, not number crunching.

Categories