Merge pull request #20 from jabadia/jabadia

better dependency management using dojo AMD
This commit is contained in:
Andy 2013-12-16 08:03:35 -08:00
commit 75f6c89a64
11 changed files with 399 additions and 632 deletions

View File

@ -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

View File

@ -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

View File

@ -1,25 +1,25 @@
"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)
{
@ -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)

View File

@ -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)()
}

View File

@ -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
View 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;
});

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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
View 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;
});

View File

@ -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;
},
}