geotile_sdk
Version:
A tile sdk for Cloud Optimised Geotiff and shapefile
410 lines (379 loc) • 12.4 kB
JavaScript
/*
* @Description: 地图渲染和瓦片提取, png编码
* @Author: luojun1
* @Date: 2021-10-21 17:25:12
* @LastEditTime: 2021-12-15 15:50:32
*/
var mapnik = require('mapnik-atlas')
var path = require('path')
var fs = require('fs')
var SRS = require('./SRS')
const zlib = require('zlib')
// const stringRandom = require('string-random')
mapnik.register_default_fonts()
mapnik.register_default_input_plugins()
var _styleDir = path.join(process.cwd(), 'style')
// 通过程序接口动态加载图层
function addLayer (map, filepath, EPSG) {
var layertype = 'gdal'
if (path.extname(filepath) === '.shp') {
layertype = 'shape'
} else if (path.extname(filepath) === '.geojson') {
layertype = 'geojson'
}
var options = {
type: layertype,
file: filepath
}
// create a layer
var layer = new mapnik.Layer('world')
layer.styles = ['My Style']
layer.srs = '+init=epsg:' + EPSG
try {
layer.datasource = new mapnik.Datasource(options)
} catch (err) {
console.log(err)
return err
}
map.add_layer(layer)
return null
}
function queryTile (map, requestBbox, sendRecall, tileSize, notEncode) {
map.zoomToBox(requestBbox)
if (!tileSize) {
tileSize = [256, 256]
}
var tile = new mapnik.Image(tileSize[0], tileSize[1])
map.render(tile, function (err, tile) {
if (err) {
console.error('render failed')
var blank = []
sendRecall(err, blank)
}
if (notEncode) {
sendRecall(err, tile)
} else {
// var start = new Date().getTime()
tile.encode('png', function (err, buffer) {
// var processTime = new Date().getTime()
// console.log('###encode Time:', processTime - start)
sendRecall(err, buffer)
})
}
})
}
function queryVTile (map, requestBbox, x, y, z, sendRecall) {
var vtile = new mapnik.VectorTile(Number(z), Number(x), Number(y))
map.extent = requestBbox // [-11271098.442818949,4696291.017841229,-11192826.925854929,4774562.534805249];
map.render(vtile, {}, function (err, vtile) {
if (err) {
console.error('render failed')
var blank = []
sendRecall(err, blank)
}
zlib.deflate(vtile.getData(), (err, data) => {
sendRecall(err, data)
})
})
}
//* ******两种获取瓦片方法**********/
// 方法1:给定单个瓦片的xyz,一次获取单个瓦片
function fetchTile (x, y, z, xmlUUID, format, sendRecall) {
if (xmlUUID != null && (xmlUUID)) {
var xmlPath = path.join(_styleDir, xmlUUID + '.xml')
fs.access(xmlPath, fs.constants.F_OK, (err) => {
if (err) {
var blank = []
console.error('access failed')
sendRecall(err, blank)
return
}
var map = new mapnik.Map(256, 256)
map.load(xmlPath, function (err, map) {
if (err) {
console.error('loading failed: ', err)
var blank = []
sendRecall(err, blank)
return
}
// var err=addLayer(map, filepath, EPSG)
map.aspect_fix_mode = mapnik.Map.ASPECT_RESPECT
var requestBbox = SRS.tile2BBox([Number(x), Number(y), Number(z)])
// console.log("requestBbox:",requestBbox)
console.log('format:', format)
if (format === 'mvt') {
queryVTile(map, requestBbox, x, y, z, sendRecall)
} else {
queryTile(map, requestBbox, sendRecall)
}
})
})
} else {
var err = new Error('lack of xmlUUID')
var blank = []
sendRecall(err, blank)
}
}
// 方法2:给定多个瓦片的连续范围,一次获取多个瓦片
function fetchTiles (paddingRegion, z, xmlUUID, sendRecall) {
var tileSize = [256, 256]
var paddingRegionSize = [tileSize[0] * (paddingRegion[1] - paddingRegion[0]), tileSize[1] * (paddingRegion[3] - paddingRegion[2])]
if (xmlUUID != null && (xmlUUID)) {
var xmlPath = path.join(_styleDir, xmlUUID + '.xml')
fs.access(xmlPath, fs.constants.F_OK, (err) => {
if (err) {
var blank = []
console.error('access failed')
sendRecall(err, blank)
return
}
var map = new mapnik.Map(paddingRegionSize[0], paddingRegionSize[1])
map.load(xmlPath, function (err, map) {
if (err) {
console.error('loading failed: ', err)
var blank = []
sendRecall(err, blank)
return
}
// var err=addLayer(map, filepath, EPSG)
map.aspect_fix_mode = mapnik.Map.ASPECT_RESPECT
var requestBbox = SRS.tile2BBox([Number(paddingRegion[0]), Number(paddingRegion[2]), Number(z)], [Number(paddingRegion[1]), Number(paddingRegion[3]), Number(z)])
// console.log("requestBbox:",requestBbox)
queryTile(map, requestBbox, sendRecall, paddingRegionSize, true) // 暂不编码png
})
})
} else {
var err = new Error('lack of xmlUUID')
var blank = []
sendRecall(err, blank)
}
}
// 从大瓦片中获取小瓦片
function getBlock (Bigtile, offsetX, offsety, width, height, recall) {
Bigtile.resize(width, height, {scaling_method: mapnik.imageScaling.near,
filter_factor: 1.0,
offset_x: Number(offsetX),
offset_y: Number(offsety),
offset_width: width,
offset_height: height
}, function (err, tile) {
if (err) {
console.log('getBlock:', err)
}
recall(err, tile)
})
}
// json字段定义
// var styleJson_raster = {
// 'style': {
// 'Type': 'Raster'
// },
// 'layer': {
// 'path': '/luojun/github/sl-component-vis-raster-v2/test/data/clip2_cog.tif',
// 'SRS': '4326'
// }
// }
// var styleJson_polygon = {
// 'style': {
// 'Type': 'Polygon',
// 'Color': 'black',
// 'LineWidth': 1,
// 'FillColor': '#bee826'
// },
// 'layer': {
// 'path': '/luojun/github/geotiff-server/test/data/world_merc.json',
// 'SRS': '3857'
// }
// }
// var styleJson_line = {
// 'style': {
// 'Type': 'Line',
// 'Color': '#bee826',
// 'LineWidth': 1
// },
// 'layer': {
// 'path': '/luojun/github/geotiff-server/test/data/world_merc.json',
// 'SRS': '3857'
// }
// }
// var styleJson_point = {
// 'style': {
// 'Type': 'Point',
// 'Color': 'black',
// 'LineWidth': 1
// },
// 'layer': {
// 'path': '/luojun/github/geotiff-server/test/data/world_merc.json',
// 'SRS': '3857'
// }
// }
function generateXML (jsonStr, recall) {
var builder = require('xmlbuilder')
const random = require('string-random')
var xmlUUID = random(8, {numbers: false})
const mapConfig = JSON.parse(jsonStr)
var xml = builder.create('Map')
.att('srs', '+init=epsg:3857')
if (Array.isArray(mapConfig.style)) {
for (let i = 0; i < mapConfig.style.length; i++) {
const featureType = mapConfig.style[i].Type
switch (featureType) {
case 'Polygon':
xml = xml.ele('Style', {'name': mapConfig.style[i].styleName})
.ele('Rule')
.ele('PolygonSymbolizer', {'fill': mapConfig.style[i].FillColor}).up()
.ele('LineSymbolizer', {'stroke': mapConfig.style[i].Color, 'stroke-width': mapConfig.style[i].LineWidth}).up()
.up()
.up()
break
case 'Line':
xml = xml.ele('Style', {'name': mapConfig.style[i].styleName})
.ele('Rule')
.ele('LineSymbolizer', {'stroke': mapConfig.style[i].Color, 'stroke-width': mapConfig.style[i].LineWidth}).up()
.up()
.up()
break
case 'Point':
xml = xml.ele('Style', {'name': mapConfig.style[i].styleName})
.ele('Rule')
.ele('PointSymbolizer').up()
.up()
.up()
break
case 'Raster':
xml = xml.ele('Style', {'name': mapConfig.style[i].styleName})
.ele('Rule')
.ele('RasterSymbolizer').up()
.up()
.up()
break
}
}
}
if (Array.isArray(mapConfig.layer)) {
for (let i = 0; i < mapConfig.layer.length; i++) {
xml = xml.ele('Layer', {'name': xmlUUID + i.toString(), 'srs': '+init=epsg:' + mapConfig.layer[i].SRS})
.ele('StyleName', mapConfig.layer[i].style).up()
.ele('Datasource')
.ele('Parameter', {'name': 'type'}, mapConfig.layer[i].type).up()
if (mapConfig.layer[i].hasOwnProperty('path')) {
xml = xml.ele('Parameter', {'name': 'file'}, mapConfig.layer[i].path).up()
} else {
xml = xml.ele('Parameter', {'name': 'host'}, mapConfig.layer[i].host).up()
.ele('Parameter', {'name': 'dbname'}, mapConfig.layer[i].dbname).up()
.ele('Parameter', {'name': 'user'}, mapConfig.layer[i].user).up()
.ele('Parameter', {'name': 'password'}, mapConfig.layer[i].password).up()
.ele('Parameter', {'name': 'table'}, '(' + mapConfig.layer[i].sql + ') as world').up()
}
console.log(xml)
xml = xml.up()
.up()
}
}
var xmlStr = xml.end({pretty: true})
var xmlPath = path.join(_styleDir, xmlUUID + '.xml')
fs.access(xmlPath, fs.constants.F_OK, (err) => {
if (err) {
// console.log("xmlStr:",xmlStr)
fs.writeFile(xmlPath, xmlStr, err => {
recall(err, xmlUUID)
})
} else {
recall(null, xmlUUID)
}
})
}
// function generateXML (jsonStr, recall) {
// var builder = require('xmlbuilder')
// const random = require('string-random')
// var xmlUUID = random(8, {numbers: false})
// const mapConfig = JSON.parse(jsonStr)
// const featureType = mapConfig.style.Type
// var srcType = 'gdal'
// if (path.extname(mapConfig.layer.path) === '.shp') {
// srcType = 'shape'
// } else if (path.extname(mapConfig.layer.path) === '.geojson') {
// srcType = 'geojson'
// }
// var xml = builder.create('Map')
// .att('srs', '+init=epsg:3857')
// .ele('Style', {'name': 'My Style'})
// .ele('Rule')
// switch (featureType) {
// case 'Polygon':
// xml.ele('PolygonSymbolizer', {'fill': mapConfig.style.FillColor}).up()
// .ele('LineSymbolizer', {'stroke': mapConfig.style.Color, 'stroke-width': mapConfig.style.LineWidth}).up()
// .up()
// .up()
// break
// case 'Line':
// xml.ele('LineSymbolizer', {'stroke': mapConfig.style.Color, 'stroke-width': mapConfig.style.LineWidth}).up()
// .up()
// .up()
// break
// case 'Point':
// xml.ele('PointSymbolizer').up()
// .up()
// .up()
// break
// case 'Raster':
// xml.ele('RasterSymbolizer').up()
// .up()
// .up()
// break
// }
// xml.up()
// .up()
// .ele('Layer', {'name': xmlUUID, 'srs': '+init=epsg:' + mapConfig.layer.SRS})
// .ele('StyleName', 'My Style').up()
// .ele('Datasource')
// .ele('Parameter', {'name': 'file'}, mapConfig.layer.path).up()
// .ele('Parameter', {'name': 'type'}, srcType).up()
// var xmlStr = xml.end({pretty: true})
// var xmlPath = path.join(_styleDir, xmlUUID + '.xml')
// fs.access(xmlPath, fs.constants.F_OK, (err) => {
// if (err) {
// // console.log("xmlStr:",xmlStr)
// fs.writeFile(xmlPath, xmlStr, err => {
// recall(err, xmlUUID)
// })
// } else {
// recall(null, xmlUUID)
// }
// })
// }
function setMapnikDir (styleDir, mapnikDir) {
if (mapnikDir && mapnikDir !== '') {
mapnik.settings.paths = {
'fonts': path.join(mapnikDir, 'lib/mapnik/fonts'),
'input_plugins': path.join(mapnikDir, 'lib/mapnik/input'),
'mapnik_index': path.join(mapnikDir, 'bin/mapnik-index'),
'shape_index': path.join(mapnikDir, 'bin/shapeindex')
}
mapnik.settings.env = {
'ICU_DATA': path.join(mapnikDir, 'share/icu'),
'GDAL_DATA': path.join(mapnikDir, 'share/gdal'),
'PROJ_LIB': path.join(mapnikDir, 'share/proj')
}
}
if (styleDir && styleDir !== '') _styleDir = styleDir
var err = fs.accessSync(_styleDir, fs.constants.R_OK | fs.constants.F_OK)
if (err) {
console.log(`${_styleDir} 'is not readable'`)
return err
}
err = fs.accessSync(mapnik.settings.paths.input_plugins, fs.constants.R_OK | fs.constants.F_OK)
if (err) {
console.log(`${mapnik.settings.paths.input_plugins} 'is not readable'`)
return err
}
return null
}
module.exports = {
fetchTile,
fetchTiles,
getBlock,
generateXML,
setMapnikDir,
addLayer
}