agentscript
Version:
AgentScript Model in Model/View architecture
172 lines (140 loc) • 5.94 kB
JavaScript
import * as util from '../src/utils.js'
import * as gis from '../src/gis.js'
import * as TileData from '../src/TileData.js'
import * as L from 'https://unpkg.com/leaflet@1.8.0/dist/leaflet-src.esm.js'
import elementOverlay from '../vendor/elementOverlay.js'
function defaultLeafletOptions() {
return {
// L.map uses these
div: 'map',
Z: 10,
// have javascript install css files
fetchCSS: true,
// the L.map options
// preferCanvas seems to be needed for model on top of json?
mapOptions: { zoomDelta: 0.25, zoomSnap: 0, preferCanvas: true },
// no elevation layer. 0.01 for invisible elevation layer, 1 for opaque
elevationOpacity: 0,
// model's border: set to null for no border. can also be used for fill color
bboxBorder: { color: 'red', weight: 2 },
// tiles's border: set to null for no border, uses css format for now.
tilesBorder: 'solid red 2px',
// map base layer: osm topo topo1 smooth usgs
terrain: 'topo',
// elevation tiles: redfishUSA/World, mapzen, mapbox
tiles: 'mapzen',
// Add Leaflet gis layer using the provided style
// https://leafletjs.com/reference.html#geojson
// https://leafletjs.com/reference.html#layer
json: null,
// jsonStyle must be a function: geoJsonFeature => ({color:...})
// default style is leaflet's:
// https://leafletjs.com/reference.html#path-option
jsonStyle: feature => ({
// put styles here. none => use leaflet defaults
color: 'red',
}),
jsonPopup: null,
// ToDo:
// A popup, bound to a layer. Must be a function: layer => string
// Ex: ['jsonLayer', layer => layer.feature.properties.NAME]
// see: https://leafletjs.com/reference.html#popup
// popup: [],
}
}
async function leafletInit(model, canvas, options = {}) {
if (!model.world.bbox) throw Error('leafletInit: model must use GeoWorld')
// ========== Startup: css, tile datasets, terrain layer ==========
options = Object.assign(defaultLeafletOptions(), options)
// If options.fetchCSS use JS to set Leaflet & our css files
if (options.fetchCSS) {
await util.fetchCssStyle('https://unpkg.com/leaflet/dist/leaflet.css')
await util.fetchCssStyle('./map.css')
// await util.fetchCssStyle('https://code.agentscript.org/gis/map.css')
}
// Get one of the 4 tile datasets
const tileData = TileData[options.tiles] // redfishUSA/World, mapzen, mapbox
// Name of the terrain layer (from the gis.js module)
const terrainName = options.terrain
// ========== Start of map layers: ==========
// ===== The map using the div, model center and Z. World is a GeoWorld
const center = gis.latlon(model.world.bboxCenter())
const map = L.map(options.div, options.mapOptions)
map.setView(center, options.Z)
// ===== Map's terrain
const terrainLayer = L.tileLayer(gis.template(terrainName), {
attribution: gis.attribution(terrainName),
// className: 'terrain-pane', // shows tiles borders
}).addTo(map)
// ===== elevation layer if non-zero elevationOpacity
let elevationLayer
if (options.elevationOpacity > 0) {
elevationLayer = L.tileLayer(tileData.zxyTemplate, {
opacity: options.elevationOpacity,
attribution: 'Elevation Tiles',
crossOrigin: '',
maxZoom: tileData.maxZoom, // larger mapzen zoom yields tileerror
}).addTo(map)
}
// ===== A border around the model
// Draw a border around the model's bbox
// Before done via css: this.canvas.style.border = options.canvasBorder
// where canvasBorder: '2px solid red'
// .. but prefer a map layer for keeping it all in Leaflet
let bboxLayer
if (options.bboxBorder) {
const latlngs = gis.latlon(gis.bboxCoords(model.world.bbox))
// bboxLayer = L.polyline(latlngs, options.bboxBorder).addTo(map)
bboxLayer = L.rectangle(latlngs, options.bboxBorder).addTo(map)
}
// ===== Draw a border around each tile, using css, not Leaflet
// https://gis.stackexchange.com/questions/149062/display-tile-grid-borders-with-leaflet-visual-debugging
if (options.tilesBorder !== 'solid red 2px') {
if (!options.tilesBorder) options.tilesBorder = '0px'
const root = document.documentElement
root.style.setProperty('--tile-border', options.tilesBorder)
}
// ===== A JSON layer with optional popup, fairly common
let jsonLayer
if (options.json) {
jsonLayer = L.geoJSON(options.json, {
style: options.jsonStyle,
})
if (options.jsonPopup) {
jsonLayer.bindPopup(options.jsonPopup)
}
jsonLayer.addTo(map)
}
// ===== The model layer, using Cody's ElementOverlay
// Should be last layer, on top
const [west, south, east, north] = model.world.bbox
const bounds = new L.LatLngBounds(
new L.LatLng(north, west),
new L.LatLng(south, east)
)
const ElementOverlay = elementOverlay(L)
const elementLayer = new ElementOverlay(canvas, bounds).addTo(map)
// ========== End of map layers ==========
return {
// a module of all the elevation tile urls & their datasets
TileData,
tileData, // our instance of TileData
gis, // the gis utilities module
// The leaflet map, and input for L.map
L,
map,
terrainName,
// The leaflet layerrs
terrainLayer,
elevationLayer,
elementLayer,
jsonLayer,
bboxLayer,
// The model's overlay layer
elementOverlay,
ElementOverlay,
// the input options, possibly modified above
options,
}
}
export default leafletInit