mirror of
https://github.com/Esri/offline-editor-js.git
synced 2025-12-15 15:20:05 +00:00
Merge pull request #20 from jabadia/jabadia
better dependency management using dojo AMD
This commit is contained in:
commit
75f6c89a64
@ -11,16 +11,16 @@
|
||||
- [x] IndexedDB not supported in iOS Safari (see https://developer.mozilla.org/en-US/docs/IndexedDB#Browser_compatibility and https://github.com/axemclion/IndexedDBShim, or http://nparashuram.com/IndexedDBShim/)
|
||||
- [x] Andy: We may want to look at limiting the tiles to two or three levels to help manage size/performance issues.
|
||||
+ limit maxLevel to current zoomLevel + 3 (no problem to include all levels up to level 0, it will be only 1 or 2 tiles per level)
|
||||
|
||||
- [ ] better tile estimation and limits
|
||||
- [ ] unit testing
|
||||
- [ ] reorganize code
|
||||
- [x] reorganize code
|
||||
+ partially done
|
||||
+ better dependency management
|
||||
- [ ] remove unused files (ioWorker, OfflineTileStore)
|
||||
- [x] remove unused files (ioWorker, OfflineTileStore)
|
||||
- [x] test iPad/iPhone **DONE**, it works!
|
||||
|
||||
- [ ] unit testing
|
||||
- [ ] better tile estimation and limits
|
||||
|
||||
- [ ] allow naming caches?
|
||||
- [ ] test iPad/iPhone
|
||||
- [ ] more general proxy.php
|
||||
|
||||
- [ ] non-rectangular area
|
||||
|
||||
@ -35,7 +35,8 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href=""><i class="fa fa-globe"></i> esri</a>
|
||||
<a class="navbar-brand" href="http://github.com/Esri/offline-editor-js"><i class="fa fa-html5"></i> JS Offline Mapping</s>
|
||||
<a class="navbar-brand" href="http://developers.arcgis.com"><i class="fa fa-globe"></i> esri</a>
|
||||
</div>
|
||||
<!--
|
||||
<div class="collapse navbar-collapse">
|
||||
@ -50,19 +51,11 @@
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="page-header">
|
||||
<h2><i class="fa fa-html5"></i> JS Offline Mapping</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<h3 id="mapTitle"></h3>
|
||||
<h3>Map:<span id="mapTitle">[none]</span></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -145,9 +138,6 @@
|
||||
<script src="../vendor/offline/offline.min.js"></script>
|
||||
<script src="../vendor/IndexedDBShim/dist/IndexedDBShim.min.js"></script>
|
||||
<script src="offlineProbe.js"></script>
|
||||
<script src="tilingScheme.js"></script>
|
||||
<script src="src/_base.js"></script>
|
||||
<script src="src/dbStore2.js"></script>
|
||||
<script src="main.js"></script>
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
"use strict"
|
||||
|
||||
var map;
|
||||
var basemapLayer;
|
||||
var graphics;
|
||||
var cancelRequested, startTime;
|
||||
var showTiles = false;
|
||||
|
||||
require(["esri/map",
|
||||
"esri/layers/GraphicsLayer", "esri/graphic", "esri/symbols/SimpleFillSymbol",
|
||||
"esri/dijit/Scalebar",
|
||||
"esri/arcgis/utils",
|
||||
"esri/geometry",
|
||||
"dojo/dom",
|
||||
"dojo/on",
|
||||
"dojo/query",
|
||||
"esri/dijit/Scalebar", "esri/arcgis/utils", "esri/geometry",
|
||||
"dojo/dom", "dojo/on", "dojo/query",
|
||||
"../vendor/bootstrap-map-js/src/js/bootstrapmap.js",
|
||||
"esri/urlUtils",
|
||||
"esri/geometry/webMercatorUtils",
|
||||
"esri/urlUtils", "esri/geometry/webMercatorUtils",
|
||||
"src/offlineEnabler.js",
|
||||
"dojo/dom-construct",
|
||||
"dojo/domReady!"],
|
||||
function(Map, GraphicsLayer, Graphic, SimpleFillSymbol, Scalebar, esriUtils, geometry, dom, on, query, BootstrapMap, urlUtils, webMercatorUtils,
|
||||
"dojo/dom-construct", "dojo/domReady!"],
|
||||
function(Map,
|
||||
GraphicsLayer, Graphic, SimpleFillSymbol,
|
||||
Scalebar, esriUtils, geometry,
|
||||
dom, on, query,
|
||||
BootstrapMap,
|
||||
urlUtils, webMercatorUtils,
|
||||
offlineEnabler,
|
||||
domConstruct)
|
||||
{
|
||||
{
|
||||
var scalebar;
|
||||
var symbol;
|
||||
|
||||
@ -58,18 +58,18 @@ require(["esri/map",
|
||||
|
||||
if(map.loaded)
|
||||
{
|
||||
basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
initMapParts();
|
||||
initEvents();
|
||||
updateTileSizeEstimation();
|
||||
initOffline();
|
||||
}
|
||||
else
|
||||
{
|
||||
on(map,"load",function()
|
||||
{
|
||||
basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
initMapParts();
|
||||
initEvents();
|
||||
updateTileSizeEstimation();
|
||||
initOffline();
|
||||
});
|
||||
}
|
||||
@ -105,18 +105,17 @@ require(["esri/map",
|
||||
|
||||
function initEvents()
|
||||
{
|
||||
map.on('extent-change', updateTileSizeEstimation );
|
||||
on(dojo.byId('minLevel'),'change', updateTileSizeEstimation);
|
||||
on(dojo.byId('maxLevel'),'change', updateTileSizeEstimation);
|
||||
map.on('extent-change', updateTileCountEstimation );
|
||||
on(dojo.byId('minLevel'),'change', updateTileCountEstimation);
|
||||
on(dojo.byId('maxLevel'),'change', updateTileCountEstimation);
|
||||
|
||||
var basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
dojo.byId('minLevel').value = basemapLayer.tileInfo.lods[0].level;
|
||||
dojo.byId('maxLevel').value = basemapLayer.tileInfo.lods[basemapLayer.tileInfo.lods.length-1].level;
|
||||
dojo.byId('minLevel').value = basemapLayer.minLevel = basemapLayer.tileInfo.lods[0].level;
|
||||
dojo.byId('maxLevel').value = basemapLayer.maxLevel = basemapLayer.tileInfo.lods[basemapLayer.tileInfo.lods.length-1].level;
|
||||
}
|
||||
|
||||
function initOffline()
|
||||
{
|
||||
var basemapLayer = offlineEnabler.getBasemapLayer(map);
|
||||
console.log("extending");
|
||||
offlineEnabler.extend(basemapLayer,function(success)
|
||||
{
|
||||
if(success)
|
||||
@ -131,6 +130,7 @@ require(["esri/map",
|
||||
esri.show(dojo.byId('ready-to-download-ui'));
|
||||
esri.hide(dojo.byId('downloading-ui'));
|
||||
updateOfflineUsage();
|
||||
updateTileCountEstimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -147,7 +147,6 @@ require(["esri/map",
|
||||
function updateOfflineUsage()
|
||||
{
|
||||
dojo.byId('offline-usage').innerHTML = "updating...";
|
||||
var basemapLayer = offlineEnabler.getBasemapLayer(map);
|
||||
basemapLayer.getOfflineUsage(function(usage)
|
||||
{
|
||||
console.log(usage);
|
||||
@ -157,14 +156,7 @@ require(["esri/map",
|
||||
});
|
||||
}
|
||||
|
||||
function estimateTileSize(tiledLayer)
|
||||
{
|
||||
var tileInfo = tiledLayer.tileInfo;
|
||||
|
||||
return 14000; // TODO - come up with a more precise estimation method
|
||||
}
|
||||
|
||||
function updateTileSizeEstimation()
|
||||
function updateTileCountEstimation()
|
||||
{
|
||||
console.log('updating');
|
||||
var zoomLevel = map.getLevel();
|
||||
@ -173,30 +165,19 @@ require(["esri/map",
|
||||
var minLevel = parseInt(dojo.byId('minLevel').value);
|
||||
var maxLevel = parseInt(dojo.byId('maxLevel').value);
|
||||
|
||||
if( maxLevel > zoomLevel + 3)
|
||||
if( maxLevel > zoomLevel + 3 || maxLevel > basemapLayer.maxLevel)
|
||||
{
|
||||
maxLevel = zoomLevel + 3;
|
||||
maxLevel = Math.min(basemapLayer.maxLevel, zoomLevel + 3);
|
||||
dojo.byId('maxLevel').value = maxLevel;
|
||||
}
|
||||
|
||||
var basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
var tileSize = estimateTileSize(basemapLayer);
|
||||
|
||||
var tilingScheme = new TilingScheme(basemapLayer,geometry);
|
||||
var totalEstimation = { tileCount:0, sizeBytes:0 }
|
||||
|
||||
domConstruct.empty('tile-count-table-body');
|
||||
|
||||
var totalEstimation = { tileCount:0, sizeBytes:0 }
|
||||
|
||||
for(var level=minLevel; level<=maxLevel; level++)
|
||||
{
|
||||
var cellIds = tilingScheme.getAllCellIdsInExtent(map.extent,level);
|
||||
|
||||
var levelEstimation = {
|
||||
level: level,
|
||||
tileCount: cellIds.length,
|
||||
sizeBytes: cellIds.length * tileSize
|
||||
}
|
||||
var levelEstimation = basemapLayer.getLevelEstimation(map.extent,level);
|
||||
|
||||
totalEstimation.tileCount += levelEstimation.tileCount;
|
||||
totalEstimation.sizeBytes += levelEstimation.sizeBytes;
|
||||
@ -228,7 +209,6 @@ require(["esri/map",
|
||||
dojo.byId('go-offline-btn').disabled = true;
|
||||
dojo.byId('go-online-btn').disabled = undefined;
|
||||
|
||||
var basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
basemapLayer.goOffline();
|
||||
}
|
||||
|
||||
@ -237,13 +217,11 @@ require(["esri/map",
|
||||
dojo.byId('go-offline-btn').disabled = undefined;
|
||||
dojo.byId('go-online-btn').disabled = true;
|
||||
|
||||
var basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
basemapLayer.goOnline();
|
||||
}
|
||||
|
||||
function deleteAllTiles()
|
||||
{
|
||||
var basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
basemapLayer.deleteAllTiles(function(success, err)
|
||||
{
|
||||
console.log("deleteAllTiles():", success,err);
|
||||
@ -251,7 +229,7 @@ require(["esri/map",
|
||||
if( success )
|
||||
alert("All tiles deleted");
|
||||
else
|
||||
alert("Can't delete tiles");
|
||||
alert("Can't delete tiles: " + err);
|
||||
|
||||
setTimeout(updateOfflineUsage,0); // request execution in the next turn of the event loop
|
||||
});
|
||||
@ -269,7 +247,6 @@ require(["esri/map",
|
||||
/* launch offline preparation process */
|
||||
var minLevel = parseInt(dojo.byId('minLevel').value);
|
||||
var maxLevel = parseInt(dojo.byId('maxLevel').value);
|
||||
var basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
basemapLayer.prepareForOffline(minLevel, maxLevel, map.extent, reportProgress, finishedDownloading);
|
||||
}
|
||||
|
||||
@ -321,7 +298,6 @@ require(["esri/map",
|
||||
|
||||
if( showTiles )
|
||||
{
|
||||
var basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
basemapLayer.getTilePolygons(function(polygon,err)
|
||||
{
|
||||
if(polygon)
|
||||
|
||||
@ -1,203 +0,0 @@
|
||||
/**
|
||||
* Library for handling the storage of map tiles.
|
||||
* Can use localStorage or IndexedDB. Local storage is supported in
|
||||
* more browsers (caniuse.com) however it is significantly more
|
||||
* limited in size.
|
||||
* NOTE: Uses localStorage by default. Override with useIndexedDB property.
|
||||
* NOTE: if you use IndexedDB be sure to verify if its available for use.
|
||||
* @param map
|
||||
* @constructor
|
||||
*
|
||||
* Author: Andy Gup (@agup)
|
||||
*/
|
||||
var OfflineTileStore = function(/* Map */ map) {
|
||||
|
||||
this.ioWorker = null;
|
||||
this.extend = null;
|
||||
this.storage = 0;
|
||||
this.map = map;
|
||||
this.dbStore = null //indexedDB
|
||||
this.useIndexedDB = false;
|
||||
|
||||
/**
|
||||
* Provides control over allow/disallow values to be
|
||||
* written to storage. Can be used for testing as well.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.allowCache = true;
|
||||
|
||||
/**
|
||||
* Private Local ENUMs (Constants)
|
||||
* Contains required configuration info.
|
||||
* @type {Object}
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
this._localEnum = (function(){
|
||||
var values = {
|
||||
TIMEOUT : 20, /* Seconds to wait for all tile requests to complete */
|
||||
LOCAL_STORAGE_MAX_LIMIT : 4.75, /* MB */ /* Most browsers offer default storage of ~5MB */
|
||||
LS_TILE_COUNT : "tile_count",
|
||||
WORKER_URL : "./src/ioWorker.js" /* child process for gathering tiles */
|
||||
}
|
||||
|
||||
return values;
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines total storage used for this domain.
|
||||
* @returns Number MB's
|
||||
*/
|
||||
this.getlocalStorageUsed = function(){
|
||||
var mb = 0;
|
||||
|
||||
//IE hack
|
||||
if(window.localStorage.hasOwnProperty("remainingspace")){
|
||||
//http://msdn.microsoft.com/en-us/library/ie/cc197016(v=vs.85).aspx
|
||||
mb = window.localStorage.remainingSpace/1024/1024;
|
||||
}
|
||||
else{
|
||||
for(var x in localStorage){
|
||||
//Uncomment out console.log to see *all* items in local storage
|
||||
//console.log(x+"="+((localStorage[x].length * 2)/1024/1024).toFixed(2)+" MB");
|
||||
mb += localStorage[x].length
|
||||
}
|
||||
}
|
||||
|
||||
return Math.round(((mb * 2)/1024/1024) * 100)/100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes base map and stores tiles.
|
||||
* If they are already in database they are ignored.
|
||||
*/
|
||||
this.storeLayer = function(){
|
||||
this.tileCount = 0;
|
||||
this.extendLayer(function(/* boolean */ evt){
|
||||
var ids = map.layerIds;
|
||||
var layer = map.getLayer(ids[0]);
|
||||
layer.refresh();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this.extendLayer = function(callback){
|
||||
if(this.extend == null){
|
||||
var count = 0;
|
||||
|
||||
var allow = this.allowCache;
|
||||
var worker = this.ioWorker;
|
||||
var db = database;
|
||||
var indexDB = this._useIndexedDB;
|
||||
|
||||
this.extend = dojo.extend(esri.layers.ArcGISTiledMapServiceLayer, { //extend ArcGISTiledMapServiceLayer to use localStorage if available, else use worker to request tile and store in local storage.
|
||||
|
||||
getTileUrl : function(level, row, col) {
|
||||
this.tileCount++;
|
||||
count++; //count number of tiles
|
||||
console.log("Count " + count);
|
||||
localStorage.setItem("tile_count",count);
|
||||
|
||||
var url = this._url.path + "/tile/" + level + "/" + row + "/" + col;
|
||||
|
||||
if(indexDB == true){
|
||||
database.get(url,function(event,result){
|
||||
console.log("img: " + result.img + ", event.url: " + result.url);
|
||||
if(event == true){
|
||||
console.log("in indexed db storage");
|
||||
return "data:image;base64," + result.img;
|
||||
}
|
||||
else{
|
||||
console.log("not in indexed db storage, pass url and load tile", url);
|
||||
worker.postMessage([url]);
|
||||
return url;
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
else{
|
||||
if(localStorage.getItem(url) !== null) {
|
||||
console.log("in local storage");
|
||||
return "data:image;base64," + localStorage.getItem(url);
|
||||
}
|
||||
else if(allow == true) {
|
||||
console.log("not in local storage, pass url and load tile", url);
|
||||
worker.postMessage([url]);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}});
|
||||
callback(true);
|
||||
}
|
||||
else{
|
||||
callback(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load src
|
||||
* TO-DO: Needs to be made AMD compliant!
|
||||
* @param urlArray
|
||||
* @param callback
|
||||
* @private
|
||||
*/
|
||||
this._loadScripts = function(/* Array */ urlArray, callback)
|
||||
{
|
||||
count = 0;
|
||||
for(var i in urlArray){
|
||||
try{
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = urlArray[i];
|
||||
script.onreadystatechange = function(){
|
||||
count++;
|
||||
console.log("Script loaded. " + this.src);
|
||||
if(count == urlArray.length) callback();
|
||||
};
|
||||
script.onload = function(){
|
||||
count++;
|
||||
console.log("Script loaded. " + this.src);
|
||||
if(count == urlArray.length) callback();
|
||||
};
|
||||
head.appendChild(script);
|
||||
}
|
||||
catch(err){
|
||||
console.log("_loadScripts: " + err.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.initLocalStorage = function() {
|
||||
var tempArray = [];
|
||||
var tempCount = 0;
|
||||
this.dbStore = new dbStore();
|
||||
this.ioWorker = new Worker(this._localEnum().WORKER_URL);
|
||||
this.ioWorker.onmessage = function(evt) {
|
||||
|
||||
this.storage = this.getlocalStorageUsed();
|
||||
console.log("Worker to Parent: ", evt.data[0]);
|
||||
console.log("localStorage used: " + this.getlocalStorageUsed());
|
||||
|
||||
try {
|
||||
localStorage.setItem(evt.data[0], evt.data[1]);
|
||||
tempCount++;
|
||||
tempArray.push({url:evt.data[0],img: evt.data[1]});
|
||||
} catch(error) {
|
||||
console.log('Problem adding tile to local storage. Storage might be full');
|
||||
}
|
||||
|
||||
var count = parseFloat(localStorage.getItem(this._localEnum().LS_TILE_COUNT));
|
||||
if(tempCount == count){
|
||||
localStorage.setItem(this._localEnum().LS_TILE_COUNT,0);
|
||||
database.add(tempArray,function(evt,err){
|
||||
evt == true ? console.log("Done") : console.log("init " + err);
|
||||
});
|
||||
}
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
this._init = function(){
|
||||
this.initLocalStorage();
|
||||
}.bind(this)()
|
||||
|
||||
}
|
||||
@ -1,14 +1,16 @@
|
||||
var Base64Utils = (function(){
|
||||
"use strict"
|
||||
|
||||
var d={};
|
||||
d.outputTypes={
|
||||
define([],function()
|
||||
{
|
||||
var Base64Utils={};
|
||||
Base64Utils.outputTypes={
|
||||
// summary:
|
||||
// Enumeration for input and output encodings.
|
||||
Base64:0, Hex:1, String:2, Raw:3
|
||||
};
|
||||
|
||||
// word-based addition
|
||||
d.addWords=function(/* word */a, /* word */b){
|
||||
Base64Utils.addWords=function(/* word */a, /* word */b){
|
||||
// summary:
|
||||
// add a pair of words together with rollover
|
||||
var l=(a&0xFFFF)+(b&0xFFFF);
|
||||
@ -22,7 +24,7 @@ var Base64Utils = (function(){
|
||||
var chrsz=8; // 16 for Unicode
|
||||
var mask=(1<<chrsz)-1;
|
||||
|
||||
d.stringToWord=function(/* string */s){
|
||||
Base64Utils.stringToWord=function(/* string */s){
|
||||
// summary:
|
||||
// convert a string to a word array
|
||||
|
||||
@ -33,7 +35,7 @@ var Base64Utils = (function(){
|
||||
return wa; // word[]
|
||||
};
|
||||
|
||||
d.wordToString=function(/* word[] */wa){
|
||||
Base64Utils.wordToString=function(/* word[] */wa){
|
||||
// summary:
|
||||
// convert an array of words to a string
|
||||
var s=[];
|
||||
@ -42,7 +44,7 @@ var Base64Utils = (function(){
|
||||
}
|
||||
return s.join(""); // string
|
||||
}
|
||||
d.wordToHex=function(/* word[] */wa){
|
||||
Base64Utils.wordToHex=function(/* word[] */wa){
|
||||
// summary:
|
||||
// convert an array of words to a hex tab
|
||||
var h="0123456789abcdef", s=[];
|
||||
@ -51,7 +53,7 @@ var Base64Utils = (function(){
|
||||
}
|
||||
return s.join(""); // string
|
||||
}
|
||||
d.wordToBase64=function(/* word[] */wa){
|
||||
Base64Utils.wordToBase64=function(/* word[] */wa){
|
||||
// summary:
|
||||
// convert an array of words to base64 encoding, should be more efficient
|
||||
// than using dojox.encoding.base64
|
||||
@ -69,5 +71,5 @@ var Base64Utils = (function(){
|
||||
return s.join(""); // string
|
||||
};
|
||||
|
||||
return d;
|
||||
})();
|
||||
return Base64Utils;
|
||||
});
|
||||
253
tiles/src/dbStore.js
Normal file
253
tiles/src/dbStore.js
Normal file
@ -0,0 +1,253 @@
|
||||
"use strict"
|
||||
|
||||
/**
|
||||
* Library for handling the storing of map tiles in IndexedDB.
|
||||
*
|
||||
* Author: Andy Gup (@agup)
|
||||
* Contributor: Javier Abadia (@javierabadia)
|
||||
*/
|
||||
define([],function()
|
||||
{
|
||||
var DbStore = function()
|
||||
{
|
||||
/**
|
||||
* Internal reference to the local database
|
||||
* @type {null}
|
||||
* @private
|
||||
*/
|
||||
this._db = null;
|
||||
|
||||
/**
|
||||
* Private Local ENUMs (Constants)
|
||||
* Contains required configuration info.
|
||||
* @type {Object}
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
this._localEnum = (function(){
|
||||
var values = {
|
||||
DB_NAME : "offline_tile_store"
|
||||
}
|
||||
|
||||
return values;
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines if indexedDB is supported
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.isSupported = function(){
|
||||
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
|
||||
if(!window.indexedDB){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object to the database
|
||||
* @param urlDataPair
|
||||
* @param callback callback(boolean, err)
|
||||
*/
|
||||
this.add = function(urlDataPair,callback){
|
||||
try{
|
||||
//console.log("add()",urlDataPair);
|
||||
var transaction = this._db.transaction(["tilepath"],"readwrite");
|
||||
|
||||
transaction.oncomplete = function(event) {
|
||||
callback(true);
|
||||
};
|
||||
|
||||
transaction.onerror = function(event) {
|
||||
callback(false,event.target.error.message)
|
||||
};
|
||||
|
||||
var objectStore = transaction.objectStore("tilepath");
|
||||
var request = objectStore.put(urlDataPair);
|
||||
request.onsuccess = function(event) {
|
||||
//console.log("item added to db " + event.target.result);
|
||||
};
|
||||
}
|
||||
catch(err){
|
||||
console.log("dbstore: " + err.stack);
|
||||
callback(false,err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a record.
|
||||
* @param url
|
||||
* @param callback
|
||||
*/
|
||||
this.get = function(/* String */ url,callback){
|
||||
if(this._db != null){
|
||||
|
||||
var objectStore = this._db.transaction(["tilepath"]).objectStore("tilepath");
|
||||
var request = objectStore.get(url);
|
||||
request.onsuccess = function(event)
|
||||
{
|
||||
var result = event.target.result;
|
||||
if(result == null){
|
||||
callback(false,"not found");
|
||||
}
|
||||
else{
|
||||
callback(true,result);
|
||||
}
|
||||
}
|
||||
request.onerror = function(err){
|
||||
callback(false,err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes entire database
|
||||
* @param callback callback(boolean, err)
|
||||
*/
|
||||
this.deleteAll = function(callback){
|
||||
if(this._db != null){
|
||||
var request = this._db.transaction(["tilepath"],"readwrite")
|
||||
.objectStore("tilepath")
|
||||
.clear();
|
||||
request.onsuccess = function(event){
|
||||
callback(true);
|
||||
}
|
||||
request.onerror = function(err){
|
||||
callback(false,err);
|
||||
}
|
||||
}
|
||||
else{
|
||||
callback(false,null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an individual entry
|
||||
* @param url
|
||||
* @param callback callback(boolean, err)
|
||||
*/
|
||||
this.delete = function(/* String */ url,callback){
|
||||
if(this._db != null){
|
||||
var request = this._db.transaction(["tilepath"],"readwrite")
|
||||
.objectStore("tilepath")
|
||||
.delete(url);
|
||||
request.onsuccess = function(event){
|
||||
callback(true);
|
||||
}
|
||||
request.onerror = function(err){
|
||||
callback(false,err);
|
||||
}
|
||||
}
|
||||
else{
|
||||
callback(false,null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all tiles from indexeddb
|
||||
* @param callback callbakck(url, err)
|
||||
*/
|
||||
this.getAllTiles = function(callback){
|
||||
if(this._db != null)
|
||||
{
|
||||
var transaction = this._db.transaction(["tilepath"])
|
||||
.objectStore("tilepath")
|
||||
.openCursor();
|
||||
|
||||
transaction.onsuccess = function(event)
|
||||
{
|
||||
var cursor = event.target.result;
|
||||
if(cursor){
|
||||
var url = cursor.value.url;
|
||||
callback(url);
|
||||
cursor.continue();
|
||||
}
|
||||
else{
|
||||
callback(null, "end");
|
||||
}
|
||||
}.bind(this);
|
||||
transaction.onerror = function(err){
|
||||
callback(null, err);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(null, "no db");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a rough, approximate size of database in MBs.
|
||||
* @param callback callback(size, null) or callback(null, error)
|
||||
*/
|
||||
this.size = function(callback){
|
||||
if(this._db != null){
|
||||
var usage = { size: 0, tileCount: 0 };
|
||||
|
||||
var transaction = this._db.transaction(["tilepath"])
|
||||
.objectStore("tilepath")
|
||||
.openCursor();
|
||||
|
||||
transaction.onsuccess = function(event){
|
||||
var cursor = event.target.result;
|
||||
if(cursor){
|
||||
var storedObject = cursor.value;
|
||||
var json = JSON.stringify(storedObject);
|
||||
usage.size += this.stringBytes(json);
|
||||
usage.tileCount += 1;
|
||||
cursor.continue();
|
||||
}
|
||||
else{
|
||||
usage.size = Math.round((usage.size/1024/1024) * 100)/100; /* JAMI: *2 */
|
||||
callback(usage,null);
|
||||
}
|
||||
}.bind(this);
|
||||
transaction.onerror = function(err){
|
||||
callback(null,err);
|
||||
}
|
||||
}
|
||||
else{
|
||||
callback(null,null);
|
||||
}
|
||||
}
|
||||
|
||||
this.stringBytes = function(str) {
|
||||
var b = str.match(/[^\x00-\xff]/g);
|
||||
return (str.length + (!b ? 0: b.length));
|
||||
}
|
||||
|
||||
this.init = function(callback)
|
||||
{
|
||||
var request = indexedDB.open(this._localEnum().DB_NAME, 4);
|
||||
callback = callback? callback : function(success) { console.log("DbStore::init() success:", success)}.bind(this);
|
||||
|
||||
request.onerror = function(event)
|
||||
{
|
||||
console.log("indexedDB error: " + event.target.errorCode);
|
||||
callback(false,event.target.errorCode);
|
||||
}.bind(this);
|
||||
|
||||
request.onupgradeneeded = function(event)
|
||||
{
|
||||
var db = event.target.result;
|
||||
|
||||
if( db.objectStoreNames.contains("tilepath"))
|
||||
{
|
||||
db.deleteObjectStore("tilepath");
|
||||
}
|
||||
|
||||
var objectStore = db.createObjectStore("tilepath", { keyPath: "url" });
|
||||
}.bind(this);
|
||||
|
||||
request.onsuccess = function(event)
|
||||
{
|
||||
this._db = event.target.result;
|
||||
console.log("database opened successfully");
|
||||
callback(true);
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
return DbStore;
|
||||
});
|
||||
@ -1,247 +0,0 @@
|
||||
/**
|
||||
* Library for handling the storing of map tiles in IndexedDB.
|
||||
*
|
||||
* Author: Andy Gup (@agup)
|
||||
* Contributor: Javier Abadia (@javierabadia)
|
||||
*/
|
||||
var DbStore = function(){
|
||||
|
||||
/**
|
||||
* Internal reference to the local database
|
||||
* @type {null}
|
||||
* @private
|
||||
*/
|
||||
this._db = null;
|
||||
|
||||
/**
|
||||
* Private Local ENUMs (Constants)
|
||||
* Contains required configuration info.
|
||||
* @type {Object}
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
this._localEnum = (function(){
|
||||
var values = {
|
||||
DB_NAME : "offline_tile_store"
|
||||
}
|
||||
|
||||
return values;
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines if indexedDB is supported
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.isSupported = function(){
|
||||
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
|
||||
if(!window.indexedDB){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object to the database
|
||||
* @param urlDataPair
|
||||
* @param callback callback(boolean, err)
|
||||
*/
|
||||
this.add = function(urlDataPair,callback){
|
||||
try{
|
||||
//console.log("add()",urlDataPair);
|
||||
var transaction = this._db.transaction(["tilepath"],"readwrite");
|
||||
|
||||
transaction.oncomplete = function(event) {
|
||||
callback(true);
|
||||
};
|
||||
|
||||
transaction.onerror = function(event) {
|
||||
callback(false,event.target.error.message)
|
||||
};
|
||||
|
||||
var objectStore = transaction.objectStore("tilepath");
|
||||
var request = objectStore.put(urlDataPair);
|
||||
request.onsuccess = function(event) {
|
||||
//console.log("item added to db " + event.target.result);
|
||||
};
|
||||
}
|
||||
catch(err){
|
||||
console.log("dbstore: " + err.stack);
|
||||
callback(false,err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a record.
|
||||
* @param url
|
||||
* @param callback
|
||||
*/
|
||||
this.get = function(/* String */ url,callback){
|
||||
if(this._db != null){
|
||||
|
||||
var objectStore = this._db.transaction(["tilepath"]).objectStore("tilepath");
|
||||
var request = objectStore.get(url);
|
||||
request.onsuccess = function(event)
|
||||
{
|
||||
var result = event.target.result;
|
||||
if(result == null){
|
||||
callback(false,"not found");
|
||||
}
|
||||
else{
|
||||
callback(true,result);
|
||||
}
|
||||
}
|
||||
request.onerror = function(err){
|
||||
callback(false,err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes entire database
|
||||
* @param callback callback(boolean, err)
|
||||
*/
|
||||
this.deleteAll = function(callback){
|
||||
if(this._db != null){
|
||||
var request = this._db.transaction(["tilepath"],"readwrite")
|
||||
.objectStore("tilepath")
|
||||
.clear();
|
||||
request.onsuccess = function(event){
|
||||
callback(true);
|
||||
}
|
||||
request.onerror = function(err){
|
||||
callback(false,err);
|
||||
}
|
||||
}
|
||||
else{
|
||||
callback(false,null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an individual entry
|
||||
* @param url
|
||||
* @param callback callback(boolean, err)
|
||||
*/
|
||||
this.delete = function(/* String */ url,callback){
|
||||
if(this._db != null){
|
||||
var request = this._db.transaction(["tilepath"],"readwrite")
|
||||
.objectStore("tilepath")
|
||||
.delete(url);
|
||||
request.onsuccess = function(event){
|
||||
callback(true);
|
||||
}
|
||||
request.onerror = function(err){
|
||||
callback(false,err);
|
||||
}
|
||||
}
|
||||
else{
|
||||
callback(false,null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all tiles from indexeddb
|
||||
* @param callback callbakck(url, err)
|
||||
*/
|
||||
this.getAllTiles = function(callback){
|
||||
if(this._db != null)
|
||||
{
|
||||
var transaction = this._db.transaction(["tilepath"])
|
||||
.objectStore("tilepath")
|
||||
.openCursor();
|
||||
|
||||
transaction.onsuccess = function(event)
|
||||
{
|
||||
var cursor = event.target.result;
|
||||
if(cursor){
|
||||
var url = cursor.value.url;
|
||||
callback(url);
|
||||
cursor.continue();
|
||||
}
|
||||
else{
|
||||
callback(null, "end");
|
||||
}
|
||||
}.bind(this);
|
||||
transaction.onerror = function(err){
|
||||
callback(null, err);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(null, "no db");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a rough, approximate size of database in MBs.
|
||||
* @param callback callback(size, null) or callback(null, error)
|
||||
*/
|
||||
this.size = function(callback){
|
||||
if(this._db != null){
|
||||
var usage = { size: 0, tileCount: 0 };
|
||||
|
||||
var transaction = this._db.transaction(["tilepath"])
|
||||
.objectStore("tilepath")
|
||||
.openCursor();
|
||||
|
||||
transaction.onsuccess = function(event){
|
||||
var cursor = event.target.result;
|
||||
if(cursor){
|
||||
var storedObject = cursor.value;
|
||||
var json = JSON.stringify(storedObject);
|
||||
usage.size += this.stringBytes(json);
|
||||
usage.tileCount += 1;
|
||||
cursor.continue();
|
||||
}
|
||||
else{
|
||||
usage.size = Math.round((usage.size/1024/1024) * 100)/100; /* JAMI: *2 */
|
||||
callback(usage,null);
|
||||
}
|
||||
}.bind(this);
|
||||
transaction.onerror = function(err){
|
||||
callback(null,err);
|
||||
}
|
||||
}
|
||||
else{
|
||||
callback(null,null);
|
||||
}
|
||||
}
|
||||
|
||||
this.stringBytes = function(str) {
|
||||
var b = str.match(/[^\x00-\xff]/g);
|
||||
return (str.length + (!b ? 0: b.length));
|
||||
}
|
||||
|
||||
this.init = function(callback)
|
||||
{
|
||||
var request = indexedDB.open(this._localEnum().DB_NAME, 4);
|
||||
callback = callback? callback : function(success) { console.log("DbStore::init() success:", success)}.bind(this);
|
||||
|
||||
request.onerror = function(event)
|
||||
{
|
||||
console.log("indexedDB error: " + event.target.errorCode);
|
||||
callback(false,event.target.errorCode);
|
||||
}.bind(this);
|
||||
|
||||
request.onupgradeneeded = function(event)
|
||||
{
|
||||
var db = event.target.result;
|
||||
|
||||
if( db.objectStoreNames.contains("tilepath"))
|
||||
{
|
||||
db.deleteObjectStore("tilepath");
|
||||
}
|
||||
|
||||
var objectStore = db.createObjectStore("tilepath", { keyPath: "url" });
|
||||
}.bind(this);
|
||||
|
||||
request.onsuccess = function(event)
|
||||
{
|
||||
this._db = event.target.result;
|
||||
console.log("database opened successfully");
|
||||
callback(true);
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
// Base64 conversion functions
|
||||
importScripts("_base.js");
|
||||
|
||||
var PROXY_URL = "../proxy.php?";
|
||||
|
||||
// Parent to worker
|
||||
onmessage = function(evt) {
|
||||
getImages(evt.data);
|
||||
};
|
||||
|
||||
function getImages(urls) {
|
||||
for (var i = 0; i < urls.length; i++) {
|
||||
var imgBytes = getImage(urls[i]);
|
||||
if (imgBytes) {
|
||||
var encoded = Base64Utils.wordToBase64(Base64Utils.stringToWord(imgBytes));
|
||||
postMessage([ urls[i], encoded ]);
|
||||
}
|
||||
} // loop
|
||||
}
|
||||
|
||||
function getImage(url) {
|
||||
url = PROXY_URL + url;
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", url, false);
|
||||
req.overrideMimeType("text/plain; charset=x-user-defined");
|
||||
req.send(null);
|
||||
|
||||
if (req.status != 200) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return req.responseText;
|
||||
}
|
||||
@ -1,10 +1,12 @@
|
||||
/*
|
||||
* depends upon "_base.js"
|
||||
*/
|
||||
"use strict"
|
||||
|
||||
define([
|
||||
"dojo/query",
|
||||
"esri/geometry"
|
||||
], function(query, geometry)
|
||||
"esri/geometry",
|
||||
"src/base64utils.js",
|
||||
"src/dbStore.js",
|
||||
"src/tilingScheme.js"
|
||||
], function(query, geometry,Base64Utils,DbStore,TilingScheme)
|
||||
{
|
||||
return {
|
||||
/*
|
||||
@ -85,10 +87,31 @@ define([
|
||||
return tileid;
|
||||
};
|
||||
|
||||
layer.estimateTileSize = function()
|
||||
{
|
||||
var tileInfo = this.tileInfo;
|
||||
|
||||
return 14000; // TODO - come up with a more precise estimation method
|
||||
};
|
||||
|
||||
layer.getLevelEstimation = function(extent, level)
|
||||
{
|
||||
var tilingScheme = new TilingScheme(this,geometry);
|
||||
var cellIds = tilingScheme.getAllCellIdsInExtent(extent,level);
|
||||
var tileSize = this.estimateTileSize();
|
||||
|
||||
var levelEstimation = {
|
||||
level: level,
|
||||
tileCount: cellIds.length,
|
||||
sizeBytes: cellIds.length * tileSize
|
||||
}
|
||||
|
||||
return levelEstimation;
|
||||
};
|
||||
|
||||
layer.prepareForOffline = function(minLevel, maxLevel, extent, reportProgress, finishedDownloading)
|
||||
{
|
||||
/* create list of tiles to store */
|
||||
var basemapLayer = map.getLayer( map.layerIds[0] );
|
||||
var tilingScheme = new TilingScheme(this,geometry);
|
||||
var cells = [];
|
||||
|
||||
|
||||
69
tiles/src/tilingScheme.js
Normal file
69
tiles/src/tilingScheme.js
Normal file
@ -0,0 +1,69 @@
|
||||
"use strict"
|
||||
|
||||
define([
|
||||
"esri/geometry"
|
||||
], function(geometry)
|
||||
{
|
||||
|
||||
var TilingScheme = function(layer)
|
||||
{
|
||||
this.tileInfo = layer.tileInfo;
|
||||
}
|
||||
|
||||
TilingScheme.prototype =
|
||||
{
|
||||
getCellIdFromXy: function(x,y,level)
|
||||
{
|
||||
var col = Math.floor((x-this.tileInfo.origin.x) / (this.tileInfo.cols*this.tileInfo.lods[level].resolution));
|
||||
var row = Math.floor((this.tileInfo.origin.y-y) / (this.tileInfo.rows*this.tileInfo.lods[level].resolution));
|
||||
return [col,row];
|
||||
},
|
||||
|
||||
getCellPolygonFromCellId: function(cellId,level)
|
||||
{
|
||||
var col1 = cellId[0];
|
||||
var row1 = cellId[1];
|
||||
var col2 = col1+1;
|
||||
var row2 = row1+1;
|
||||
|
||||
var x1 = this.tileInfo.origin.x + (col1 * this.tileInfo.cols * this.tileInfo.lods[level].resolution);
|
||||
var y1 = this.tileInfo.origin.y - (row1 * this.tileInfo.rows * this.tileInfo.lods[level].resolution);
|
||||
var x2 = this.tileInfo.origin.x + (col2 * this.tileInfo.cols * this.tileInfo.lods[level].resolution);
|
||||
var y2 = this.tileInfo.origin.y - (row2 * this.tileInfo.rows * this.tileInfo.lods[level].resolution);
|
||||
|
||||
var polygon = new geometry.Polygon(this.tileInfo.spatialReference);
|
||||
polygon.addRing([
|
||||
[x1,y1], // clockwise
|
||||
[x2,y1],
|
||||
[x2,y2],
|
||||
[x1,y2],
|
||||
[x1,y1],
|
||||
]);
|
||||
return polygon;
|
||||
},
|
||||
|
||||
getAllCellIdsInExtent : function(extent, gridLevel)
|
||||
{
|
||||
var cellId0 = this.getCellIdFromXy(extent.xmin, extent.ymin, gridLevel);
|
||||
var cellId1 = this.getCellIdFromXy(extent.xmax, extent.ymax, gridLevel);
|
||||
|
||||
var i,j;
|
||||
var i0 = Math.max(Math.min(cellId0[0], cellId1[0]), this.tileInfo.lods[gridLevel].startTileCol);
|
||||
var i1 = Math.min(Math.max(cellId0[0], cellId1[0]), this.tileInfo.lods[gridLevel].endTileCol);
|
||||
var j0 = Math.max(Math.min(cellId0[1], cellId1[1]), this.tileInfo.lods[gridLevel].startTileRow);
|
||||
var j1 = Math.min(Math.max(cellId0[1], cellId1[1]), this.tileInfo.lods[gridLevel].endTileRow);
|
||||
|
||||
var cellIds = [];
|
||||
|
||||
for(i=i0; i<=i1; i++)
|
||||
for(j=j0; j<=j1; j++)
|
||||
{
|
||||
cellIds.push([i,j]);
|
||||
}
|
||||
|
||||
return cellIds;
|
||||
},
|
||||
}
|
||||
|
||||
return TilingScheme;
|
||||
});
|
||||
@ -1,62 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var TilingScheme = function(layer,geometry)
|
||||
{
|
||||
this.tileInfo = layer.tileInfo;
|
||||
this.geometry = geometry;
|
||||
}
|
||||
|
||||
TilingScheme.prototype =
|
||||
{
|
||||
getCellIdFromXy: function(x,y,level)
|
||||
{
|
||||
var col = Math.floor((x-this.tileInfo.origin.x) / (this.tileInfo.cols*this.tileInfo.lods[level].resolution));
|
||||
var row = Math.floor((this.tileInfo.origin.y-y) / (this.tileInfo.rows*this.tileInfo.lods[level].resolution));
|
||||
return [col,row];
|
||||
},
|
||||
|
||||
getCellPolygonFromCellId: function(cellId,level)
|
||||
{
|
||||
var col1 = cellId[0];
|
||||
var row1 = cellId[1];
|
||||
var col2 = col1+1;
|
||||
var row2 = row1+1;
|
||||
|
||||
var x1 = this.tileInfo.origin.x + (col1 * this.tileInfo.cols * this.tileInfo.lods[level].resolution);
|
||||
var y1 = this.tileInfo.origin.y - (row1 * this.tileInfo.rows * this.tileInfo.lods[level].resolution);
|
||||
var x2 = this.tileInfo.origin.x + (col2 * this.tileInfo.cols * this.tileInfo.lods[level].resolution);
|
||||
var y2 = this.tileInfo.origin.y - (row2 * this.tileInfo.rows * this.tileInfo.lods[level].resolution);
|
||||
|
||||
var polygon = new this.geometry.Polygon(this.tileInfo.spatialReference);
|
||||
polygon.addRing([
|
||||
[x1,y1], // clockwise
|
||||
[x2,y1],
|
||||
[x2,y2],
|
||||
[x1,y2],
|
||||
[x1,y1],
|
||||
])
|
||||
return polygon;
|
||||
},
|
||||
|
||||
getAllCellIdsInExtent : function(extent, gridLevel)
|
||||
{
|
||||
var cellId0 = this.getCellIdFromXy(extent.xmin, extent.ymin, gridLevel);
|
||||
var cellId1 = this.getCellIdFromXy(extent.xmax, extent.ymax, gridLevel);
|
||||
|
||||
var i,j;
|
||||
var i0 = Math.max(Math.min(cellId0[0], cellId1[0]), this.tileInfo.lods[gridLevel].startTileCol);
|
||||
var i1 = Math.min(Math.max(cellId0[0], cellId1[0]), this.tileInfo.lods[gridLevel].endTileCol);
|
||||
var j0 = Math.max(Math.min(cellId0[1], cellId1[1]), this.tileInfo.lods[gridLevel].startTileRow);
|
||||
var j1 = Math.min(Math.max(cellId0[1], cellId1[1]), this.tileInfo.lods[gridLevel].endTileRow);
|
||||
|
||||
var cellIds = [];
|
||||
|
||||
for(i=i0; i<=i1; i++)
|
||||
for(j=j0; j<=j1; j++)
|
||||
{
|
||||
cellIds.push([i,j]);
|
||||
}
|
||||
|
||||
return cellIds;
|
||||
},
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user