Skip to content

added GeoDataSet.js #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
5 changes: 3 additions & 2 deletions gis/LeafletDataSet.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// import * as TileData from './TileData.js'
import * as util from '../src/utils.js'
import * as gis from '../src/gis.js'
import GeoDataSet from '../src/GeoDataSet.js'
// import RGBDataSet from './RGBDataSet.js'

class LeafletDataSet {
Expand Down Expand Up @@ -91,8 +92,8 @@ class LeafletDataSet {
const dataSetMatrix = this.dataSetsMatrix(tilesBBox, z)
const tilesDataSet = this.dataSetMatrixToDataSet(dataSetMatrix)
const cropParameters = this.getCropParameters(bbox, z)
const bboxDataSet = tilesDataSet.crop(cropParameters)
bboxDataSet.bbox = bbox
const croppedDataSet = tilesDataSet.crop(cropParameters)
const bboxDataSet = GeoDataSet.viewFromDataSet(croppedDataSet, bbox)

console.log('bbox', bbox)
console.log('tilesBBox', tilesBBox)
Expand Down
2 changes: 1 addition & 1 deletion models/DropletsModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default class DropletsModel extends Model {
// 'patchAspect',
// 'dataSetAspectNearest',
// 'dataSetAspectBilinear',
stepType = 'minNeighbor'
stepType = 'dataSetAspectNearest'
moves // how many moves in a step
// Installed datasets:
elevation
Expand Down
2 changes: 1 addition & 1 deletion models/scripts/DropletsModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class DropletsModel extends Model {
// 'patchAspect',
// 'dataSetAspectNearest',
// 'dataSetAspectBilinear',
stepType = 'minNeighbor'
stepType = 'dataSetAspectNearest'
moves // how many moves in a step
// Installed datasets:
elevation
Expand Down
2 changes: 2 additions & 0 deletions src/AS.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ export { default as Mouse } from './Mouse.js'
export * as gis from './gis.js'
export * as geojson from './geojson.js'
export { default as GeoWorld } from './GeoWorld.js'
export { default as GeoDataSet } from './GeoDataSet.js'

1 change: 1 addition & 0 deletions src/AS0.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ export { default as Mouse } from './Mouse.js'
export * as gis from './gis.js'
export * as geojson from './geojson.js'
export { default as GeoWorld } from './GeoWorld.js'
export { default as GeoDataSet } from './GeoDataSet.js'
182 changes: 182 additions & 0 deletions src/GeoDataSet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import DataSet from "./DataSet.js"
import { bboxMetricSize } from './gis.js'
class GeoDataSet extends DataSet {

/**
* Mostly the same a DataSet except it has bounds.
* A few methods, like slope, dzdx, dzdy, and aspect are different because they take into account the size of the bbox in meters
*
* @param {Number} width width of the DataSet in pixels
* @param {Number} height height of the DataSet in pixels
* @param {Array} bbox [west, south, east, north]
* @param {TypedArray} data
*/
constructor(width, height, bbox, data) {
super(width, height, data)
this.bbox = bbox
}

/**
* Create a view of a Dataset including the bounds.
*
* @static
* @param {DataSet} dataSet
* @param {Array} bbox [west, south, east, north]
* @returns {GeoDataSet} GeoDataSet view of the dataset data. It is a view not a copy.
*/
static viewFromDataSet(dataSet, bbox) {
return new GeoDataSet(dataSet.width, dataSet.height, bbox, dataSet.data)
}

lat2y(lat) {
const [west, south, east, north] = this.bbox
const y = Math.round(this.height * (lat - south) / (north - south))
return y
}

lon2x(lng) {
const [west, south, east, north] = this.bbox
const x = Math.round(this.width * (lng - west) / (east - west))
return x
}

// Convert from geoon/lat coords to pixel coords
toPixel(geoX, geoY) {
return [this.lon2x(geoX), this.lat2y(geoY)]
}

// Get pixel from geo lon/lat coords to pixel coords
getGeo(geoX, geoY) {
const [x, y] = this.toPixel(geoX, geoY)
return this.getXY(x, y)
}

// Set pixel from geo lon/lat coords to pixel coords
// I think this could be a slow way to set data for large amounts
setGeo(geoX, geoY, value) {
const [x, y] = this.toPixel(geoX, geoY)
return this.setXY(x, y, value)
}

/**
* Samples a pixel at a given latitude and longitude
*
* @param {Number} geoX longitude
* @param {Number} geoY latitude
* @param {Boolean} useNearest
* @throws Out Of Range Error - when it is outside of the bbox
* @returns {Number}
*/
sampleGeo(geoX, geoY, useNearest = true) {
const [x, y] = this.toPixel(geoX, geoY)
return this.sample(x, y, useNearest)
}

/**
* Change in z value in terms of x. Finite difference.
*
* @returns {GeoDataset}
*/
dzdx() {
const [widthMeters, heightMeters] = bboxMetricSize(this.bbox)
const pixelScale = widthMeters / this.width
const dzdx = super.dzdx(2, (1 / 8) * (1 / pixelScale)) // (1/8) for the kernel and 1/pixelscale to get units right
const dzdx2 = GeoDataSet.viewFromDataSet(dzdx, this.bbox)
return dzdx2
}

/**
* Change in z value in terms of y. Finite difference.
*
* @returns {GeoDataSet}
*/
dzdy() {
const [widthMeters, heightMeters] = bboxMetricSize(this.bbox)
const pixelScale = heightMeters / this.height
const dzdy = super.dzdy(2, (1 / 8) * (1 / pixelScale))
const dzdy2 = GeoDataSet.viewFromDataSet(dzdy, this.bbox)
return dzdy2
}

/**
* Create dzdx, dzdy, slope, and aspect in one function.
*
* @returns {SlopeAndAspect} {dzdx, dzdy, slope, aspect}
*/
slopeAndAspect() {
const dzdx = this.dzdx()
const dzdy = this.dzdy()
const slope = this.slope(dzdx, dzdy)
const aspect = this.aspect(dzdx, dzdy)
return { dzdx, dzdy, slope, aspect }
}

/**
* The aspect of each pixel in radians.
*
* @param {GeoDataset} dzdx
* @param {GeoDataSet} dzdy
* @returns {GeoDataSet} Aspect in radians.
*/
aspect(dzdx = this.dzdx(), dzdy = this.dzdy()) {
const asp = dzdx.map((x, i) => {
const y = dzdy.data[i]
const a = Math.atan2(-y, -x)
return a
})
return asp
}

/**
* Returns the slope in radians
*
* @param {GeoDataset} dzdx
* @param {GeoDataset} dzdy
* @returns {GeoDataset}
*/
slope(dzdx = this.dzdx(), dzdy = this.dzdy()) {
const slop = dzdx.map((x, i) => {
const y = dzdy.data[i]
const a = Math.hypot(-x, -y)
const sl = (Math.PI / 2) - Math.atan2(1, a)
return sl
})
return slop
}

//
// The functions below are the same as DataSet's version except they return a GeoDataset instead of a dataset.
// Is there a better way to do this?
//
//
clone() {
return new GeoDataSet(this.width, this.height, this.bbox, this.data)
}

resample(width, height, useNearest = true, Type = Array) {
const a = super.resample(width, height, useNearest, Type)
const b = GeoDataSet.viewFromDataSet(a, this.bbox)
return b
}

convolve(kernel, factor = 1, crop = false) {
const a = super.convolve(kernel, factor, crop)
const b = GeoDataSet.viewFromDataSet(a, this.bbox)
return b
}

normalize(lo = 0, hi = 1, round = false) {
const a = super.normalize(lo, hi, round)
const b = GeoDataSet.viewFromDataSet(a, this.bbox)
return b
}

map(f) {
const a = super.map(f)
const b = GeoDataSet.viewFromDataSet(a, this.bbox)
return b
}
}

export default GeoDataSet

6 changes: 5 additions & 1 deletion src/TileData.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as util from './utils.js'
import RGBDataSet from './RGBDataSet.js'
import GeoDataSet from './GeoDataSet.js'
import {xyz2bbox} from './gis.js'

// ============= rgb to elevation/numeric =============

Expand Down Expand Up @@ -28,7 +30,9 @@ const sharedTileObject = {
},
zxyToDataSet: async function (z, x, y, ArrayType = Float32Array) {
const img = await this.zxyToTile(z, x, y)
return this.tileDataSet(img, ArrayType)
const ds1 = this.tileDataSet(img, ArrayType)
const bbox = xyz2bbox(x, y, z)
return GeoDataSet.viewFromDataSet(ds1, bbox)
},
tileDataSet: function (img, ArrayType = Float32Array) {
const tileDecoder = this.elevationFcn
Expand Down
2 changes: 1 addition & 1 deletion test/samples.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"ants": "{\"ticks\":500,\"model\":[\"world\",\"patches\",\"turtles\",\"links\",\"ticks\",\"step0\",\"step\",\"toRads\",\"fromRads\",\"toAngleRads\",\"fromAngleRads\",\"toCCW\",\"population\",\"speed\",\"maxPheromone\",\"diffusionRate\",\"evaporationRate\",\"wiggleAngle\",\"foodX\",\"foodY\",\"nestX\",\"nestY\"],\"patches\":6561,\"patch\":{\"id\":4174,\"isFood\":false,\"isNest\":false,\"foodPheromone\":0.0257680536425004,\"nestPheromone\":0.04021862087665316,\"_diffuseNext\":0,\"neighbors\":[4092,4093,4094,4175,4256,4255,4254,4173]},\"turtles\":255,\"turtle\":{\"id\":80,\"theta\":0.35187919106928417,\"x\":4.569458552541104,\"y\":1.1474232505464783,\"carryingFood\":true,\"pheromone\":0.37713422825070403},\"links\":0}",
"buttons": "{\"ticks\":500,\"model\":[\"world\",\"patches\",\"turtles\",\"links\",\"ticks\",\"step0\",\"step\",\"toRads\",\"fromRads\",\"toAngleRads\",\"fromAngleRads\",\"toCCW\",\"population\",\"cluster\",\"done\"],\"patches\":1089,\"patch\":{\"id\":273},\"turtles\":200,\"turtle\":{\"id\":136,\"theta\":2.283142302887487,\"x\":-10,\"y\":-16,\"links\":[112,150,412,438]},\"links\":500,\"link\":{\"id\":482,\"end0\":{\"id\":121,\"theta\":3.7783750618803937,\"x\":10,\"y\":9,\"links\":[194,200,482,484]},\"end1\":{\"id\":192,\"theta\":5.1900867760030405,\"x\":-15,\"y\":-8,\"links\":[77,274,366,454,482,499]}}}",
"diffuse": "{\"ticks\":500,\"model\":[\"world\",\"patches\",\"turtles\",\"links\",\"ticks\",\"step0\",\"step\",\"toRads\",\"fromRads\",\"toAngleRads\",\"fromAngleRads\",\"toCCW\",\"population\",\"speed\",\"wiggleAngle\",\"radius\",\"diffuseRate\",\"seedDelta\",\"seedMax\"],\"patches\":80601,\"patch\":{\"id\":45451,\"ran\":0.6014832474734432,\"_diffuseNext\":0,\"neighbors\":[45049,45050,45051,45452,45853,45852,45851,45450]},\"turtles\":2,\"turtle\":{\"id\":1,\"theta\":4.017593726538558,\"x\":-169.46259631353462,\"y\":-13.334433990431691},\"links\":0}",
"droplets": "{\"ticks\":500,\"model\":[\"world\",\"patches\",\"turtles\",\"links\",\"ticks\",\"step0\",\"step\",\"toRads\",\"fromRads\",\"toAngleRads\",\"fromAngleRads\",\"toCCW\",\"speed\",\"stepType\",\"moves\",\"elevation\",\"dzdx\",\"dzdy\",\"slope\",\"aspect\",\"localMins\"],\"patches\":10201,\"patch\":{\"id\":5057,\"elevation\":1723.09375,\"aspect\":4.24928055747772,\"neighbors\":[4955,4956,4957,5058,5159,5158,5157,5056],\"isLocalMin\":false},\"turtles\":10201,\"turtle\":{\"id\":7694,\"theta\":0.6867621740671395,\"x\":-29.91422888528167,\"y\":-19.749582869876278,\"done\":true},\"links\":0}",
"droplets": "{\"ticks\":500,\"model\":[\"world\",\"patches\",\"turtles\",\"links\",\"ticks\",\"step0\",\"step\",\"toRads\",\"fromRads\",\"toAngleRads\",\"fromAngleRads\",\"toCCW\",\"speed\",\"stepType\",\"moves\",\"elevation\",\"dzdx\",\"dzdy\",\"slope\",\"aspect\",\"localMins\"],\"patches\":10201,\"patch\":{\"id\":5057,\"elevation\":1723.09375,\"aspect\":4.24928055747772,\"neighbors\":[4955,4956,4957,5058,5159,5158,5157,5056],\"isLocalMin\":false},\"turtles\":9584,\"turtle\":{\"id\":7632,\"theta\":1.4478503085133343,\"x\":5.4447649576110795,\"y\":-24.843932317600657,\"done\":true},\"links\":0}",
"exit": "{\"ticks\":500,\"model\":[\"world\",\"patches\",\"turtles\",\"links\",\"ticks\",\"step0\",\"step\",\"toRads\",\"fromRads\",\"toAngleRads\",\"fromAngleRads\",\"toCCW\",\"numExits\",\"population\",\"exits\",\"inside\",\"wall\"],\"patches\":5041,\"patch\":{\"id\":4012,\"neighbors4\":[3941,4013,4083,4011],\"turtles\":[],\"neighbors\":[3940,3941,3942,4013,4084,4083,4082,4011]},\"turtles\":9,\"turtle\":{\"id\":1039,\"theta\":-0.7853981633974483,\"x\":15,\"y\":-22,\"exit\":{\"id\":4313,\"exitNumber\":0,\"turtles\":[]}},\"links\":0}",
"fire": "{\"ticks\":500,\"model\":[\"world\",\"patches\",\"turtles\",\"links\",\"ticks\",\"step0\",\"step\",\"toRads\",\"fromRads\",\"toAngleRads\",\"fromAngleRads\",\"toCCW\",\"density\",\"fires\",\"embers\",\"patchTypes\",\"dirtType\",\"treeType\",\"fireType\",\"burnedTrees\",\"initialTrees\"],\"patches\":63001,\"patch\":{\"id\":52543,\"type\":\"ember0\",\"neighbors4\":[52292,52544,52794,52542]},\"turtles\":0,\"links\":0}",
"flock": "{\"ticks\":500,\"model\":[\"world\",\"patches\",\"turtles\",\"links\",\"ticks\",\"step0\",\"step\",\"toRads\",\"fromRads\",\"toAngleRads\",\"fromAngleRads\",\"toCCW\",\"population\",\"vision\",\"speed\",\"maxTurn\",\"minSeparation\"],\"patches\":1089,\"patch\":{\"id\":901,\"turtles\":[]},\"turtles\":100,\"turtle\":{\"id\":62,\"theta\":8.580787915677849,\"x\":-0.5027269158976337,\"y\":-6.08771206064941},\"links\":0}",
Expand Down
135 changes: 135 additions & 0 deletions views3d/avalanche.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<html>

<head>
<title>Avalanche</title>
<link rel="icon" type="image/x-icon" href="../favicon.ico" />
</head>

<body>
<script type="module">
import * as util from '../src/utils.js'
import World from '../src/World.js'
import Model from '../src/Model.js'
import { mapzen as provider } from '../src/TileData.js'

import TwoDraw from '../src/TwoDraw.js'
import Animator from '../src/Animator.js'
import Color from '../src/Color.js'
import ColorMap from '../src/ColorMap.js'
import DataSet from '../src/DataSet.js'
import GeoDataSet from '../src/GeoDataSet.js'
import { xyz2bbox, bboxMetricSize } from '../src/gis.js'
import ThreeDraw from '../src/ThreeDraw.js'

const pi = Math.PI
/**
*
* Model
*
* **/
export default class AvalancheModel extends Model {
// ======================
constructor(worldOptions = World.defaultOptions(50)) {
super(worldOptions)
}

// data can be gis zxy or a DataSet
async startup() {
const z = 13, x = 1555, y = 3084
const bounds = xyz2bbox(x, y, z)// alta utah. USA
const elev = await provider.zxyToDataSet(z, x, y)
this.installDataSets(elev)
}

installDataSets(elevation) {
const { slope, aspect, dzdx, dzdy } = elevation.slopeAndAspect()
this.patches.importDataSet(aspect, 'aspect')
this.patches.importDataSet(slope, 'slope')
this.patches.importDataSet(dzdx, 'dzdx')
this.patches.importDataSet(dzdy, 'dzdy')
this.patches.importDataSet(elevation, 'elevation')
// for 3d
const elevationScaled = elevation.clone().scale(-10, 10)
this.patches.importDataSet(elevationScaled, 'z', true) // for drawing
}
setup() {
this.patches.ask(p => {
p.snowDepth = 1
})
}
step() {
// start new buffer
this.patches.ask(p => p.nextSnow = p.snowDepth)
// drop snow
this.patches.ask(p => p.nextSnow += + 0.01)
// make avalanche
this.patches.ask(p => {
const maxSnowDepth = (pi) / (p.slope)
if (p.snowDepth > maxSnowDepth) {
//choose the 2 downhill neighbors to give snow to
const n = Math.min(2, p.neighbors.length)
p.neighbors.minNOf(n, 'elevation')
.ask(p2 => p2.nextSnow = p2.nextSnow + p.snowDepth / n)
p.nextSnow = 0
}
})
// swap buffer
this.patches.ask(p => p.snowDepth = p.nextSnow)
}
}


/**
*
* View
*
* */
const snowColor = ColorMap.gradientColorMap(20, ['rgb(98,52,18)', 'white'])
const drawOptions = {
patchesMesh: 'PointsMesh',
patchesSize: 4,
patchesColor: p => {
const aspect2 = (p.aspect + 2*pi) % (2*pi)
const k = (pi - Math.abs(aspect2 - pi))/pi
const snow = snowColor.scaleColor(p.snowDepth, 0, 6)
const col = Color.typedColor(k * snow[0], k * snow[1], k * snow[2])
return col
},
}

const model = new AvalancheModel()
await model.startup()
model.setup()

const view = new ThreeDraw(
model,
{ div: 'modelDiv' },
drawOptions
)

// const view = new TwoDraw(
// model,
// {
// div: 'modelDiv',
// patchSize: 4,
// useSprites: true, // lots of turtles, sprites faster
// },
// drawOptions
// )

const anim = new Animator(
() => {
model.step()
view.draw()
},
400, // run 500 steps
30 // 30 fps
)

util.toWindow({ util, model, view, anim, ColorMap, Color })

</script>
<div id="modelDiv"></div>
</body>

</html>