agentscript
Version:
AgentScript Model in Model/View architecture
306 lines (267 loc) • 11.6 kB
HTML
<html>
<head>
<title>Droplets</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css">
<link rel="stylesheet" href="./map.css">
</head>
<body>
<div id="map"></div>
<script type="module">
import * as util from '../src/utils.js'
import * as gis from '../src/gis.js'
import * as TileData from '../src/TileData.js'
import * as leafletUtils from './leafletUtils.js'
import BBoxDataSet from '../src/BBoxDataSet.js'
import TwoDraw from '../src/TwoDraw.js'
import Animator from '../src/Animator.js'
import GUI from '../src/GUI.js'
import Model from '../models/DropletsModel.js'
import * as L from 'https://unpkg.com/leaflet@1.8.0/dist/leaflet-src.esm.js'
import elementOverlay from '../vendor/elementOverlay.js'
// await util.fetchCssStyle('https://unpkg.com/leaflet/dist/leaflet.css')
// await util.fetchCssStyle('./map.css')
const ElementOverlay = elementOverlay(L)
const tileData = TileData['mapzen'] // redfishUSA/World, mapzen, mapbox
const terrainName = 'topo'
// ================ create map with terrain & elevation ================
const center = [35.67, -105.93]
const Z = 10 // 10 11
const map = L.map('map', {
zoomDelta: 0.25, //0.1
zoomSnap: 0,
preferCanvas: true,
// renderer: L.canvas()
}).setView(center, Z)
const terrainLayer = L.tileLayer(gis.template(terrainName), {
attribution: gis.attribution(terrainName),
// className: 'terrain-pane' // shows tiles borders
}).addTo(map)
const elevationLayer = L.tileLayer(
tileData.zxyTemplate,
{
opacity: 0.3,
attribution: 'Elevation Tiles',
crossOrigin: '',
maxZoom: tileData.maxZoom, // larger mapzen zoom yields tileerror
}
).addTo(map)
// util.toWindow({ terrainLayer, elevationLayer, map })
// ================ create dataset from bbox tiles ================
// let leafletDataSet = new LeafletDataSet(
// map,
// elevationLayer,
// // tileData
// )
// leafletUtils.testEvents(map, elevationLayer)
// leafletUtils.testZooms(elevationLayer)
// ================ create model using bbox dataset ================
let modelLayer, model, view, anim //, drawOptions
async function setupModelView(bounds = map.getBounds()) {
let bbox = gis.Lbounds2bbox(bounds)
let zoom = Math.round(map.getZoom())
let bboxDataSet = await new BBoxDataSet() // default tileData, tileSize
.getBBoxDataSet(bbox, zoom)
// const bboxDataSet = await leafletDataSet.getBBoxDataSet(bounds)
const bboxExtent = bboxDataSet.extent()
// util.toWindow({ leafletDataSet, bboxDataSet, bboxExtent, bounds })
// console.log('leafletDataSet', leafletDataSet, 'bboxDataSet', bboxDataSet)
const patchSize = 4
const worldOptions = {
minX: 0,
minY: 0,
maxX: Math.round(bboxDataSet.width / patchSize) - 1,
maxY: Math.round(bboxDataSet.height / patchSize) - 1,
}
if (model) model.reset(worldOptions)
else model = new Model(worldOptions)
// await model.startup([Z, X, Y]) // TEST
await model.startup(bboxDataSet)
model.setup()
model.elementBounds = bounds
model.bboxDataSet = bboxDataSet
// drawOptions = drawOptions || {
const drawOptions = view?.drawOptions || {
patchesMap: 'Jet', // 'transparent',
initPatches: (model, view) => {
const colorMap = view.drawOptions.patchesMap
const colors = model.patches.map(
p => p.isLocalMin
? 'black'
: colorMap.scaleColor(p.elevation, ...bboxExtent))
return colors
},
turtlesShape: 'square',
turtlesRotate: false,
turtlesColor: 'rgb(255,0,0)', // 'red',
turtlesSize: 0.8,
}
// view = view || new TwoDraw(model, {
view = new TwoDraw(model, {
div: util.createCanvas(0, 0), // the view will resize
patchSize: patchSize,
useSprites: true,
drawOptions: drawOptions, // will be view.drawOptions if available
})
// drawOptions = view.drawOptions
// Note: when zooming happens after adding the canvas, remove the current one
// and draw it on a new larger canvas with ctx.imageSmoothingEnabled = false
if (modelLayer) modelLayer.remove()
view.canvas.style.border = '1px red solid'
modelLayer = new ElementOverlay(view.canvas, bounds).addTo(map)
// const modelLayer = new ElementOverlay(view.canvas, gis.bbox2bounds(bbox)).addTo(map)
if (!anim) {
anim = new Animator(
() => {
model.step()
view.draw()
// gui.update()
if (model.moves === 0) {
anim.stop()
console.log('No moves, stopping at step', model.ticks)
}
},
-1, //500,// -1, // 500, // run 500 steps
30 // 10 // 30 // 30 fps
).startStats('70px') // avoid +/= leaflet control
anim.setIdle(() => view.draw())
} else {
anim.reset()
}
util.toWindow({ model, view, modelLayer, anim, drawOptions })
}
await setupModelView() // default to full page
// await setupModelView(gis.santaFeBBox) // test
// ================ mouse drag bbox rect ================
const mapMouseDownFcn = (ev) => {
if (ev.originalEvent.altKey) {
leafletUtils.mouseBounds(L, map, ev, async (bounds) => {
await setupModelView(bounds)
})
}
}
map.on('mousedown', mapMouseDownFcn)
const mapKeyFcn = async (ev) => {
// console.log('key', ev.originalEvent)
if (ev.originalEvent.key === 'm') {
await setupModelView()
}
}
map.on('keypress', mapKeyFcn)
// ================ gui ================
const gui = new GUI({
restartMap: {
button: async () => {
const atEdge = model.turtles.getDefault('atEdge')
await setupModelView()
gui.controllers.pause.name('pause')
model.turtles.setDefault('atEdge', atEdge)
}
},
pause: {
button: () => {
const isRunning = anim.isRunning()
gui.controllers.pause.name(isRunning ? 'resume' : 'pause')
isRunning ? anim.stop() : anim.start()
},
},
stepType: {
chooser: [model.stepType,
['minNeighbor', 'patchAspect', 'dataSetAspectNearest', 'dataSetAspectBilinear',
]],
cmd: val => (model.stepType = val),
},
patchesColors: {
// val: ['Jet', ['transparent', 'Jet', 'LightGray']],
chooser: ['Transparent', ['Transparent', 'Jet', 'Gray', 'Hue', 'Basic16']],
cmd: val => {
view.drawOptions.patchesMap = val
view.resetOptions(view.drawOptions)
// drawOptions = view.resetOptions(drawOptions)
if (!anim.isRunning()) view.draw()
},
},
'Opacity': {
// puddleDepth: {
// val: [model.puddleDepth, [1, 15, 1]],
// cmd: async val => {
// model.puddleDepth = val
// }
// },
tilesOpacity: {
chooser: [elevationLayer.options.opacity, [0, 1, 0.1]],
cmd: val => elevationLayer.setOpacity(val),
},
modelOpacity: {
slider: [1, [0, 1, 0.1]],
cmd: val => view.canvas.style.opacity = val,
},
},
'Model Data': {
modelTicks: {
monitor: [model, 'ticks']
},
patches: {
monitor: [model.patches, 'length']
// cmd: () => model.patches.length,
},
turtlesSize: {
slider: [0.8, [.2, 1, 0.1]],
cmd: val => {
view.drawOptions.turtlesSize = val
if (!anim.isRunning()) view.draw()
},
},
turtlesSpeed: {
slider: [model.speed, [0.2, 1, 0.1]],
cmd: val => model.speed = val,
},
turtlesColor: {
color: view.drawOptions.turtlesColor,
cmd: val => {
view.drawOptions.turtlesColor = val
if (!anim.isRunning()) view.draw()
},
},
moves: {
monitor: [model, 'moves'],
// cmd: () => model.moves
},
atEdge: {
chooser: [model.turtles.getDefault('atEdge'),
['wrap', 'die', 'clamp', 'bounce', 'random']],
cmd: val => {
model.turtles.setDefault('atEdge', val)
},
},
},
'Downloads': {
getElevations: {
button: () => util.downloadBlob(model.bboxDataSet, 'elevations.json', false),
},
patchesAspect: {
button: () => {
const dataSet = model.patches.exportDataSet('aspect')
dataSet.bounds = model.elementBounds // add metadata
util.downloadBlob(dataSet, 'patchesAspect.json', false)
}
},
turtlesXYHead: {
button: () => {
const data = model.turtles.map(t => {
let { x, y, heading } = t
const obj = { x, y, heading }
util.forLoop(obj, (val, key) => obj[key] = Math.round(val))
return obj
})
util.downloadBlob(data, 'turtlesXYHead.json', false)
}
},
}
})
// util.forLoop(gui.folders, (val) => val.open())
// gui.folders.mapFolder.open()
// gui.baseGui.close()
// util.toWindow({ GUI: gui.GUI, gui })
</script>
</body>
</html>