I'm making a web application that has to work offline. So far everything works and my last step is to take the map tiles offline. Luckily I know exactly what areas of the map will need to be accessible to users, so I don't have to allow caching of millions of tiles.
The map is split into areas and so the idea is to offer the tiles for these areas as downloadable 'packages.'
For instance, when I'm online, I go to the 'tile packages' page, which offers downloads for several areas. I choose the area which I'm interested in, it downloads the tiles, and when I go offline, I'm able to use these tiles. I only need about 2 zoom levels, one far out for quick navigation, and one more up close for more detail.
I'm using leaflet to serve up the map. Has anyone had to do something like this and could give me some guidance? I really just don't know how to even approach this, and it's the last piece of the puzzle.
Sadly you don't point out, what the exact problem is or at which step you fail. So I will try to give a general answer:
Leaflet uses Tiles by different providers to for a slippymap using JS. The map tiles (aka rasterimages) can be offered via an Tile Map Service (TMS) or an slightly different method (for OSM the numbering here described).
So you can create a list of images you want to get and can transfer them by respeciting legal and tecnical terms. For OSM this is for example:
http://wiki.openstreetmap.org/wiki/Legal_FAQ
https://wiki.openstreetmap.org/wiki/Tile_usage_policy
So you need to create an server/client script, that is able to do such a bulk transfer (maybe as packed archive file?) and ask to place it at a certain place for your user. I'm not experienced enough in Leaflet and can't tell you how to provide them, beside you might add them to the browsers cache itself, or to use a local server to provide them as localhost.
Anyway, if you have more questions, just ask.
So here's what I came up with. I import an area of the map to my database. I then offer this section as a downloadable package. When the user downloads the package, the database is queried and returns all tiles associated with that area in JSON format. The images are stored as blobs. I then pass this array of tiles to a custom leaflet layer which parses the data. Here's the code for the layer:
define([], function() {
L.TileLayer.IDBTiles = L.TileLayer.extend({
initialize: function(url, options, tiles) {
options = L.setOptions(this, options);
// detecting retina displays, adjusting tileSize and zoom levels
if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
options.tileSize = Math.floor(options.tileSize / 2);
options.zoomOffset++;
if (options.minZoom > 0) {
options.minZoom--;
}
this.options.maxZoom--;
}
this._url = url;
var subdomains = this.options.subdomains;
if (typeof subdomains === 'string') {
this.options.subdomains = subdomains.split('');
}
this.tiles = tiles;
},
getTileUrl: function (tilePoint) {
this._adjustTilePoint(tilePoint);
var z = this._getZoomForUrl();
var x = tilePoint.x;
var y = tilePoint.y;
var result = this.tiles.filter(function(row) {
return (row.value.tile_column === x
&& row.value.tile_row === y
&& row.value.zoom_level === z);
});
if(result[0]) return result[0].value.tile_data;
else return;
}
});
});
I think you can use a quadtree,i.e. space filling curve. MS Bing Map uses the most simple tile map: http://bcdcspatial.blogspot.de/2012/01/onlineoffline-mapping-map-tiles-and.html?m=1. I think the other maps server also uses a space filling curve, buf it's not so obvious. You may search for ms bings maps quadkey or nick's spatial index hilbert curve. You can also download my php class hilbert curve # phpclasses.org. You can use it with many different space filling curves and to generate a quadkey. A good start is also the hacker's cookbook. There is a whole chapter dedicated to the hilbert curve.
Related
I am using Google Apps Script to create a page, on which I would like to embed maps. The maps themselves would be static, but the map could be different depending on other parameters (it’s a genealogy page, and I’d like to display a map of birth and death locations, and maybe some other map points, based on a selected individual).
Using Google’s Maps service, I know that I can create a map, with a couple points built in.
Function getMapImage() {
var map = Maps.newStaticMap()
.setSize(600,400)
.addMarker('Chicago, Illinois') // markers would be based on a passed parm; this is just test data
.addMarker('Pocatello, Idaho');
// *** This is where I am looking for some guidance
return(); // obviously, I'm not returning a blank for real
}
Within the map class, there are a number of things I can do with it at this point.
I could create a URL, and pass that back. That appears to require an API account, which at this point, I do not have (and ideally, would like to avoid, but maybe I’ll have to do that). It also appears that I will run into CORB issues with that, which I think is beyond my knowledge (so if that’s the solution, I’ll be back for more guidance).
I could create a blob as an image, and pass that back to my page. I have tried this using a few different examples I have found while researching this.
Server Side
function getMapImage() {
var map = Maps.newStaticMap()
.setSize(600,400)
.addMarker('Chicago, Illinois')
.addMarker('Pocatello, Idaho');
var mapImage = map.getAs("image/png");
// OR
// var mapImage = map.getBlob();
return(mapImage);
}
Page side
<div id=”mapDiv”></div>
<script>
$(function() {
google.script.run.withSuccessHandler(displayMap).getMapImage();
}
function displayMap(mapImage) {
var binaryData = [];
binaryData.push(mapImage);
var mapURL = window.URL.createObjectURL(new Blob(binaryData, {type: "image/png"}))
var mapIMG = "<img src=\'" + mapURL + "\'>"
$('#mapDiv').html(mapIMG);
}
</script>
The page calls getMapImage() on the server, and the return data is sent as a parm to displayMap().
var mapIMG ends up resolving to <img src='blob:https://n-a4slffdg23u3pai7jxk7xfeg4t7dfweecjbruoa-0lu-script.googleusercontent.com/51b3d383-0eef-41c1-9a50-3397cbe83e0d'> This version doesn't create any errors in the console, which other options I tried did. But on the page, I'm just getting the standard 16x16 image not found icon.
I’ve tried a few other things based on what I’ve come across in researching this, but don’t want to litter this post with all sorts of different code snippets. I’ve tried a lot of things, but clearly not the right thing yet.
What’s the best / correct (dare I ask, simplest) way to build a map with Google’s Map class, and then serve it to a web page?
EDIT: I added a little more detail on how the server and page interact, in response to Tanaike's question.
Modification points:
I think that in your script, Blob is returned from Google Apps Script to Javascript using google.script.run. Unfortunately, in the current stage, Blob data cannot be directly sent from from Google Apps Script to Javascript. I think that this might be the reason of your issue.
In this case, I would like to propose to directly create the data URL at the Google Apps Script side. When your script is modified, it becomes as follows.
Modified script:
Google Apps Script side:
function getMapImage() {
var map = Maps.newStaticMap()
.setSize(600, 400)
.addMarker('Chicago, Illinois')
.addMarker('Pocatello, Idaho');
var blob = map.getAs("image/png"); // or map.getBlob()
var dataUrl = `data:image/png;base64,${Utilities.base64Encode(blob.getBytes())}`;
return dataUrl;
}
Javascript side:
$(function() {
google.script.run.withSuccessHandler(displayMap).getMapImage();
});
function displayMap(mapURL) {
var mapIMG = "<img src=\'" + mapURL + "\'>"
$('#mapDiv').html(mapIMG);
}
In your Javascript side, $(function() {google.script.run.withSuccessHandler(displayMap).getMapImage();} is not enclosed by ). Please be careful this.
Note:
In my environment, when I saw <div id=”mapDiv”></div>, this double quote ” couldn't be used. So if in your environment, an error occurs by <div id=”mapDiv”></div>, please modify ” to " like <div id="mapDiv"></div>.
Reference:
base64Encode(data)
According to the documentation I can pre-define the set of markers and other objects while creating the map, which then will be displayed. I do not wish to put all the possible markers/images/rectangles/etc in the JS code. I've read that people calculate the visible area on every tiles moving/zooming, do a HTTP request and server returns needed markers.
However, I would like to use another way, as it is a little more efficient:
1. For example, currently Leaflet automatically asks for the tiles 0:0 and 0:1;
2. In addition it could make a HTTP request and ask the server: "Hey, give me also the markers for the tiles 0:0 and 0:1".
3. Completely remove markers which are on the tiles which have become invisible.
Are the steps 2-3 possible and how if Yes?
What you're asking looks pretty similar to how Leaflet.VectorGrid works: each "tile" request is a request for vector data, not for a raster image. In fact, perhaps using protobuffer vector tiles is the right approach for your scenario.
VectorGrid relies on the logic implemented by L.GridLayer to handle the tile loading/unloading logic.
If you insist on doing this yourself, I suggest reading the Leaflet tutorials on creating plugins first; have a look to the source code for Leaflet's L.GridLayer and for VectorGrid to see how those work, and then something like:
L.GridLayer.MarkerLoader = L.GridLayer.extend({
initialize: funcion(url, options) {
this._url = url;
this._markerStore = {};
L.GridLayer.prototype.initialize.call(this, options);
},
createTile: function(coords, done) {
var key = this._tileCoordsToKey(coords);
var data = {
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
z: coords.z
};
var tileUrl = L.Util.template(this._url, L.extend(data, this.options));
fetch(tileUrl).then(function(response){
// Parse the response, with either response.json()
// or response.text() or response.blob().
// See https://developer.mozilla.org/en-US/docs/Web/API/Response
// Create a bunch of markers based on the parsed response
// The specific syntax depends on the format of the data structure
var markers = data.map(function(point){
return L.marker(point);
});
// Add those markers to a L.LayerGroup, add that LayerGroup
// to a dictionary (to remove it later), and finally add it to the map
var group = L.layerGroup(markers);
this._markerStore[key] = group;
// Mark the tile as ready
done();
});
// Return an empty <div> as a tile. Real data will be loaded async and
// put in LayerGroups anyway.
return L.DomUtil.createElement('div');
},
_removeTile: function(key) {
// Whenever a tile is pruned, remove the corresponding LayerGroup
// from the map and from the _markerStore dictionary
this._markerStore[key].remove();
delete this._markerStore[key];
return L.GridLayer.prototype._removeTile.call(this, key);
}
});
In an effort to reduce clientside load, we are attempting to do the work of flattening Paper.js layers on a Node Express server. We have many layers to flatten with lots of image data. And rather than overwriting our data structure, we want to end up with new objects containing the rasterized (flattened) layers.
So we have an Express route that looks like this:
app.post('/flatten', function (request, response) {
var pdfs = JSON.parse(request.body.pdfs);
// Attempt to set up canvas on the server side to work with
var canvas = new paper.Canvas(1000, 1000);
paper.setup(canvas);
paper.view.draw();
for (var i = 0; i < pdfs.length; i++) {
var pdf = pdfs[i];
if (pdf !== null) {
for (var j = 0; j < pdf.pages.length; j++) {
if (pdf.pages[j].layer !== undefined) {
paper.project.layers.push(pdf.pages[j].layer); // Attempt to add to current project; necessary?
pdf.pages[j].layer.activate(); // Blows up
pdf.pages[j].layer.visible = true;
var layerAsRaster = pdf.pages[j].layer.rasterize(); // Blows up
layerAsRaster.visible = false;
var dataString = layerAsRaster.toDataURL();
pdfs[i].pages[j].pageImageData = dataString.split(',')[1];
pdf.pages[j].layer.visible = false;
}
}
}
}
response.send(pdfs);
});
The .layer is a native Paper.js layer that was made on the clientside.
We receive this error when hitting this route:
TypeError: pdf.pages[j].layer.activate is not a function
Thinking that perhaps we don't need to worry about activating layers on the serverside, I commented that out, but got the same error for the .rasterize line. (See the two lines commented "Blows up".)
Do I need to somehow import the layers we're receiving from the client into the project? I attempt to do that with the line:
paper.project.layers.push(pdf.pages[j].layer);
but to no avail.
How can I modify this method to successfully work with layers on the serverside?
The problem is that you are directly adding the layer to the project with the line paper.project.layers.push(pdf.pages[j].layer);
You're not allowed to directly manipulate paper's data structures. If you want to add a layer to a project use the following (note that this is not documented and will change with the next release of paper, but I don't think you'll need to do this):
(paperscript)
project.addChild(layer);
(javascript)
paper.project.addChild(layer);
It's not clear how pdf.pages[i].layer was created on the server side, whether it was imported via JSON (in which case it could already be inserted into the project), or whether it was removed from another project, so there may be other complications.
I think there is another problem. It doesn't appear that pdf.pages[i].layer has been turned into a server-side layer. So the key question is how was it transferred from the client to the server?
Here's a stab at the whole process:
(client side)
jsonLayer = paper.project.activeLayer.exportJSON();
// send jsonLayer to server using some method
(server side)
// get jsonLayer from client
layer = new paper.Layer();
layer.importJSON(jsonLayer);
layer should already be inserted into the project and should contain all the items that were in jsonLayer which was the layer on the client.
Here's a link to a discussion on how importJSON and exportJSON map to one another:
paperjs group discussion
I am going to implement a dynamic legend using JavaScript in Adobe Acrobat.
The document contains a lot of layers. Every layer has an own legend. The origin idea is to implement the legend so, that it contains the images in a dialog box for the visible layers.
I can only hide/show the layers by setting state to false or true (this.getOCGs()[i].state = false;) on document-level.
Question 1: Can I extract data from layer somehow for legend establishing? I think no, as we only have these function on layers: getIntent(), setIntent() and setAction(). Right? Therefore I decided to arrange it so, that all needed icons for every layer are saved in a folder with corresponding names. JavaScript should import the icons and I build the a dialog window with icons of visible Layers and place a text(description for this icon).
I tried all possibilities of image import described here: http://pubhelper.blogspot.com.au/2012/07/astuces-toolbar-icons-et-javascript.html. I got only one way (Convert the icons as hexadecimal strings). This way isn't good, as it is too much work to create with an other tool a hexadecimal string from a images and place it into a javascript code.
Unfortunately, I cannot import image using other methods:(. Since the security settings in Adobe are changed after version 7 or so, it is not possible to use functions like app.newDoc, app.openDoc, even app.getPath On document-level. I decided to implement the import on a folder level using trusted functions like this:
Variant 1:
var importImg = app.trustedFunction(function() {
app.beginPriv();
var myDoc = app.newDoc({
nWidth: 20,
nHeight: 20
});
var img = myDoc.importIcon("icon", "/icon.png", 0);
app.endPriv();
return img; });
var oIcon = importImg();
The settings in Preferences->JavaScript-> JavaScript Security are disabled (Enable menu item JS execution privileges, enable global object security policy)
NotAllowedError: Security settings prevent access to this property or method.
App.newDoc:109:Folder-Level:User:acrobat.js
Variant 2:
var importImg = app.trustedFunction(function() {
var appPath = var phPath = app.getPath({
cCategory: "user",
cFolder: "javascript"
});
try {
app.beginPriv();
var doc = app.openDoc({
cPath: phPath + "/icon.png",
bHidden: true
});
app.endPriv();
} catch (e) {
console.println("Could not open icon file: " + e);
return;
}
var oIcon = util.iconStreamFromIcon(doc.getIcon("icon"));
return oIcon;});
var oIcon = importImg();
Could not open icon file: NotAllowedError: Security settings prevent access to this property or method.
At least it allows the execution of all these functions like app.newDoc, but in the second variant it says, wrong range of content or so. Maybe is here the pdf from an image created false? I just took the image and printed it into a pdf.
I tried all these possibilities with .jpg, .png, .pdf. with different sizes(big images and 20x20), It doesn't work.
Could somebody help me, as I spent a lot of time with trying different possibilities. It would be actually better to implement the main goal described above on document level, is it possible?
Thank you and kind regards,
Alex
Do you have the Console fully activated in Acrobat? If not, do so and look for error messages you get.
The first variant does not work, because myDoc is not defined (unless you have done that before you call the importImg function).
If you want to import the image into the newly created file, you will have to make a reference to the Document Object you create with newDoc(). Actually, that would make the link to myDoc, as in
var myDoc = app.newDoc(1,1)
(are you sure you want to create a document measuring 1x1 pt?)
The next issue with the first variant is a bug in Acrobat, which discards "floating" Icon Objects when saving the document; you'd have to attach the Icon Object to a field to keep it; this field can be hidden, or even on a hidden Template page in the document.
OK so I have a few levels of a simple platform built out,
so I just need some insight on how to assign images to bodies, and handle images, etc. The game is a simple platform game, with a ball as the palyer chacter and
you have to try to reach the other side. I have some obstacles like
joints and swinging balls, etc. Just getting it started, please let
me know if you can help... box2dweb.. Here is an example of a few
bodies inside my game in various places.. Any advice would be greatly
appreciated. Pplayer character let me know if you can help.
function PC(gamePiece){
if (gamePiece == 1) {
var ballSd1 = new b2CircleDef();
ballSd1.density = 1.1;
ballSd1.radius = 22;
ballSd1.restitution = 0.5;
ballSd1.friction = 1;
ballSd1.userData = 'player';
var ballBd = new b2BodyDef();
ballBd.linearDamping = .03;
ballBd.allowSleep = false;
ballBd.AddShape(ballSd1);
ballBd.position.Set(40,0);
player.object = world.CreateBody(ballBd);
}
ok so i am using box2d and need some help adding images to bodies...
I guess you do not generate your world programmatically. You will need to find an editor suitable for this job, or write one yourself.
I can advice you to use R.U.B.E which lets you create your Box2D worlds and attach images to the bodies. You may then export the scene via JSON which fits very well to javascript.
The rendering of those images attached to bodies will be a custom job. As well as the importing of the scene if there is no loader for box2dweb yet.