agentscript
Version:
AgentScript Model in Model/View architecture
410 lines (341 loc) • 10.6 kB
JavaScript
import * as util from '../src/utils.js'
import * as gis from '../src/gis.js'
import maplibregl from 'https://esm.sh/maplibre-gl@5.3.0'
await util.fetchCssStyle(
'https://esm.sh/maplibre-gl@5.3.0/dist/maplibre-gl.css'
)
// ========== init
// avoid user having to have fullScreen.css file
util.addCssStyle(`
body {
margin: 0;
padding: 0;
}
#map {
width: 100vw;
height: 100vh;
}
`)
export function newCanvas() {
return util.createCanvas(0, 0)
}
export function isBBox(obj) {
return gis.isBBox(obj)
}
// export async function mapLoadPromise(map) {
async function mapLoadPromise(map) {
return new Promise((resolve, reject) => {
map.on('load', () => resolve())
})
}
export function bboxCenter(bbox) {
return gis.bboxCenter(bbox)
}
export function jsonToBBox(json) {
return gis.jsonToBBox(json)
}
export const santaFeBBox = gis.santaFeBBox
export const santaFeSmallBBox = gis.santaFeSmallBBox
export const santaFeCenter = gis.santaFeCenter
export const newMexicoBBox = gis.newMexicoBBox
export const newMexicoCenter = gis.newMexicoCenter
export const usaBBox = gis.usaBBox
export const usaCenter = gis.usaCenter
// ========== map
function emptyMap(center, zoom, div) {
// "world" can be a model who's world we can use (model.world)
// if (world.world) world = world.world
const map = new maplibregl.Map({
container: div, // container id
center: center, //world.bboxCenter(),
zoom: zoom,
// renderWorldCopies: false,
style: {
version: 8,
sources: {},
layers: [],
},
})
return map
}
export async function newMap(center, zoom = 10, div = 'map') {
zoom = Math.round(zoom) // needed for tile, bbox, etc calculations
// if (isBBox(center)) center = gis.bboxCenter(center)
if (isBBox(center)) center = this.bboxCenter(center)
// await importMapLibre() // is no-op if css already loaded
const map = emptyMap(center, zoom, div)
await mapLoadPromise(map)
const nav = new maplibregl.NavigationControl()
map.addControl(nav, 'top-left')
return map
}
export function onLoad(map, msg = 'A load event occurred.') {
map.on('load', function () {
console.log(msg)
})
}
// terrain sources: osm topo topo1 smooth usgs
// elevation sources: mapzen maptiler redfishUSA redfishWorld mapbox
export function terrain(template) {
return gis.template(template)
}
export function elevation(template) {
return gis.elevationTemplate(template)
}
export function mapbbox(map) {
const bounds = map.getBounds() // Returns a LngLatBounds object
return [
bounds.getWest(), // Left longitude
bounds.getSouth(), // Bottom latitude
bounds.getEast(), // Right longitude
bounds.getNorth(), // Top latitude
]
}
export function getZoom(map, round = true) {
let zoom = map.getZoom()
if (round) zoom = Math.round(zoom)
return zoom
}
export function showTileBoundaries(map, show = true) {
map.showTileBoundaries = show
}
// ========== listeners
export function mapListener(map, fcn) {
function updateBoundingBox() {
const newBBox = mapbbox(map)
console.log('Updated Bounding Box:', newBBox)
fcn(map, newBBox)
}
// Update bounding box on map move and zoom events
map.on('moveend', updateBoundingBox)
map.on('zoomend', updateBoundingBox)
}
export function dragRectListener(map, id, callback, initBBox = null) {
if (initBBox && callback) {
// console.log('initBBox', initBBox)
callback(initBBox)
}
let startLngLat = null
let isDrawing = false
let bbox = null
function getBBoxFromPoints(start, end) {
return [
Math.min(start.lng, end.lng), // west
Math.min(start.lat, end.lat), // south
Math.max(start.lng, end.lng), // east
Math.max(start.lat, end.lat), // north
]
}
function startDrawing(event) {
if (!event.originalEvent.shiftKey) return // Require Shift key to start
event.preventDefault()
startLngLat = event.lngLat
isDrawing = true
bbox = null
}
function updateDrawing(event) {
if (!isDrawing || !startLngLat) return
bbox = getBBoxFromPoints(startLngLat, event.lngLat)
updateGeojson(map, id, bbox)
}
function stopDrawing(event) {
if (!isDrawing) return
if (bbox && callback) callback(bbox)
isDrawing = false
bbox = null
}
map.on('mousedown', startDrawing)
map.on('mousemove', updateDrawing)
map.on('mouseup', stopDrawing)
}
// ========== layers
export const getLayer = (map, id) => map.getLayer(id)
export const getLayers = map => map.getStyle().layers
export const getPaintProperties = (map, id) =>
map.getLayer(id).paint._properties.properties
export const getPaintPropertiesKeys = (map, id) =>
Object.keys(getPaintProperties(map, id))
// ========== raster layer
export function addRasterLayer(map, id, url, opacity = 1) {
map.addSource(id, {
type: 'raster',
tiles: Array.isArray(url) ? url : [url],
tileSize: 256,
attribution: '© OpenStreetMap Contributors',
})
map.addLayer({
id: id,
type: 'raster',
source: id,
paint: { 'raster-opacity': opacity },
})
}
// ========== vector layer
export function addVectorLayer(map, id, url, color = 'red', width = 3) {
map.addSource(id, {
type: 'vector',
url: url,
})
map.addLayer({
id: id,
type: 'line',
source: id,
'source-layer': id,
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': color,
'line-width': width,
},
})
}
// ========== terrain layer
export function addTerrainSource(map, id, url, maxzoom = 14) {
map.addSource(id, {
type: 'raster-dem',
tiles: Array.isArray(url) ? url : [url],
tileSize: 256,
maxzoom: maxzoom,
encoding: 'terrarium', // or 'mapbox' depending on provider
attribution: '© Elevation data provider',
})
}
// ========== geojson layer
// Note both geojson layers can share their sources due to often
// using the same geojson for lines and fills.
// Thus if the geojson arg is a string, it is used for the source id
export function addGeojsonFillLayer(map, id, geojson, fill, opacity, stroke) {
if (isBBox(geojson)) geojson = gis.bboxFeature(geojson)
// if (Array.isArray(geojson)) geojson = bboxFeature(geojson)
let sourceID = util.isString(geojson) ? geojson : id
if (sourceID === id) {
map.addSource(id, {
type: 'geojson',
data: geojson,
})
}
map.addLayer({
id: id,
type: 'fill',
source: sourceID,
paint: {
'fill-color': fill,
'fill-outline-color': stroke ? stroke : fill,
'fill-opacity': opacity ? opacity : 1,
},
})
}
export function addGeojsonLineLayer(map, id, geojson, color, width = 1) {
if (isBBox(geojson)) geojson = gis.bboxFeature(geojson)
const sourceID = util.isString(geojson) ? geojson : id
if (sourceID === id) {
map.addSource(id, {
type: 'geojson',
data: geojson,
})
}
map.addLayer({
id: id,
type: 'line',
source: sourceID,
paint: { 'line-color': color, 'line-width': width },
})
}
// Equivalent to:
// mltools.addGeojsonFillLayer(map, 'bbox', bbox, 'rgba(255, 0, 0, 0.2)')
// mltools.addGeojsonLineLayer(map, 'bboxLines', bbox, 'red', 3)
export function addGeojsonLayer(map, id, geojson, fill, stroke, width = 2) {
if (isBBox(geojson)) geojson = gis.bboxFeature(geojson)
const sourceID = util.isString(geojson) ? geojson : id
if (sourceID === id) {
map.addSource(id, {
type: 'geojson',
data: geojson,
})
}
map.addLayer({
// id: id + 'Fill',
id: id,
type: 'fill',
source: sourceID,
paint: { 'fill-color': fill },
})
map.addLayer({
id: id + 'Stroke',
type: 'line',
source: sourceID,
paint: { 'line-color': stroke, 'line-width': width },
})
}
export function updateGeojson(map, id, geojson) {
if (isBBox(geojson)) geojson = gis.bboxFeature(geojson)
map.getSource(id).setData(geojson)
}
// ========== canvas layer
export function addCanvasLayer(map, id, canvas, coords) {
// https://maplibre.org/maplibre-gl-js/docs/API/classes/CanvasSource/
if (isBBox(coords)) coords = gis.bboxCoords(coords)
map.addSource(id, {
type: 'canvas',
canvas: canvas,
coordinates: coords,
})
map.addLayer({
id: id,
type: 'raster',
source: id,
})
}
export function updateCanvas(map, id, canvas, coords) {
map.removeLayer(id)
map.removeSource(id)
addCanvasLayer(map, id, canvas, coords)
}
// ========== layer utils
export function addLayerClick(map, id, fcn) {
map.on('click', id, fcn)
}
export function addLayerClickPopup(map, id, msg) {
map.on('click', id, function (ev) {
const props = ev.features[0].properties
const html = msg(props, ev)
new maplibregl.Popup({ maxWidth: 'none' })
.setLngLat(ev.lngLat)
.setHTML(html)
.addTo(map)
})
}
let popup // each move pops up a new value
export function addLayerMovePopup(map, id, msg) {
map.on('mousemove', id, function (ev) {
const props = ev.features[0].properties
// if (props) {
if (popup) popup.remove()
const html = msg(props, ev)
popup = new maplibregl.Popup({ maxWidth: 'none' })
.setLngLat(ev.lngLat)
.setHTML(html)
.addTo(map)
// }
})
}
export function addLayerCursor(map, id, cursor = 'pointer') {
map.on('mouseenter', id, () => {
map.getCanvas().style.cursor = cursor
})
map.on('mouseleave', id, () => {
map.getCanvas().style.cursor = ''
})
}
// ========== streets
export async function fetchStreetsJson(bbox) {
return await gis.fetchStreetsJson(bbox)
}
// ========== misc
// tiles: [
// 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
// 'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png',
// 'https://c.tile.openstreetmap.org/{z}/{x}/{y}.png'
// ],