-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathDropletsModel.js
166 lines (148 loc) · 5.34 KB
/
DropletsModel.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
Note: this is the only model that has dom methods.
To keep the model/view split clean, we provide a
pre-built dataset created from a 256x256 gis tile (needing dom methods)
scaled to 101x101 and thus does not need any of the dom methods
*/
import * as util from 'https://code.agentscript.org/src/utils.js'
import World from 'https://code.agentscript.org/src/World.js'
import Model from 'https://code.agentscript.org/src/Model.js'
import tileDataSet from './data/tile101x101.js'
// Current tile dataSet functions:
// redfishUSDataSet
// redfishWorldDataSet
// mapzenDataSet
// mapboxDataSet
import { mapzen as provider } from 'https://code.agentscript.org/src/TileData.js'
import BBoxDataSet from 'https://code.agentscript.org/src/BBoxDataSet.js'
class DropletsModel extends Model {
speed = 0.5
// stepType choices:
// 'minNeighbor',
// 'patchAspect',
// 'dataSetAspectNearest',
// 'dataSetAspectBilinear',
stepType = 'minNeighbor'
moves = 0 // how many moves in a step
// Installed datasets:
elevation
dzdx
dzdy
slope
aspect
// ======================
constructor(worldOptions = World.defaultOptions(50)) {
super(worldOptions)
}
// data can be gis [z, x, y], [bbox, z], z, or a DataSet
// if world is a geoworld: [world.bbox, z]
async startup(data = tileDataSet) {
if (util.isDataSet(data)) {
// data is a dataset, use as is.
// workers: reconstruct the dataset object via
// new DataSet(width, height, data)
this.elevation = data
} else if (Array.isArray(data)) {
// && data.length === 3) {
if (data.length === 3) {
// data is [z, x, y] array
this.elevation = await provider.zxyToDataSet(...data)
} else if (data.length === 2) {
// data is [bbox, zoom]
this.elevation = await new BBoxDataSet().getBBoxDataSet(...data)
}
} else if (typeof data === 'number') {
// data is zoom; use world.bbox for bbox
const bbox = this.world.bbox
const zoom = data
if (bbox) {
const bboxDataSet = new BBoxDataSet()
this.elevation = await bboxDataSet.getBBoxDataSet(bbox, zoom)
}
}
if (!this.elevation)
throw Error(
'model startup: data argument is not one of [z, x, y], [bbox, z], z, or a DataSet'
)
}
installDataSets(elevation) {
const slopeAndAspect = elevation.slopeAndAspect()
const { dzdx, dzdy, slope, aspect } = slopeAndAspect
Object.assign(this, { elevation, dzdx, dzdy, slope, aspect })
this.patches.importDataSet(elevation, 'elevation', true)
this.patches.importDataSet(aspect, 'aspect', true)
}
setup() {
this.installDataSets(this.elevation)
// handled by step():
// this.turtles.setDefault('atEdge', 'die')
this.turtles.ask(t => (t.done = false))
this.localMins = []
this.patches.ask(p => {
p.isLocalMin =
p.neighbors.minOneOf('elevation').elevation > p.elevation
if (p.isLocalMin) this.localMins.push(p)
p.sprout(1, this.turtles)
})
}
faceDownhill(t) {
if (this.stepType === 'minNeighbor') {
// Face the best neighbor if better than me
const n = t.patch.neighbors.minOneOf('elevation')
if (t.patch.elevation > n.elevation) t.face(n)
} else if (this.stepType === 'patchAspect') {
t.theta = t.patch.aspect
} else if (this.stepType.includes('dataSet')) {
// Move in direction of aspect DataSet:
const { minXcor, maxYcor, numX, numY } = this.world
// bilinear many more minima
const nearest = this.stepType === 'dataSetAspectNearest'
t.theta = this.aspect.coordSample(
t.x,
t.y,
minXcor,
maxYcor,
numX,
numY,
nearest
)
} else {
throw Error('bad stepType: ' + this.stepType)
}
}
handleLocalMin(t) {
if (t.patch.isLocalMin) {
// center t in patch, and mark as done
t.setxy(t.patch.x, t.patch.y)
t.done = true
}
}
patchOK(t, p) {
return p.elevation < t.patch.elevation
}
step() {
if (this.done) return
this.moves = 0
this.turtles.ask(t => {
if (t.done) return
this.faceDownhill(t)
const pAhead = t.patchAtHeadingAndDistance(t.heading, this.speed)
if (!pAhead) {
t.die()
} else if (pAhead === t.patch || this.patchOK(t, pAhead)) {
t.forward(this.speed)
this.handleLocalMin(t)
this.moves++
} else {
t.done = true
}
})
this.done = this.moves === 0
if (this.done) console.log('No moves, stopping at step', this.ticks)
}
// Useful informational function: (not used by model but can be used by "app")
turtlesOnLocalMins() {
return this.localMins.reduce((acc, p) => acc + p.turtlesHere.length, 0)
}
}
export default DropletsModel