gpt4 book ai didi

javascript - mapbox-gl-js:针对给定间距将可见区域和方位调整到给定线

转载 作者:可可西里 更新时间:2023-11-01 01:19:01 25 4
gpt4 key购买 nike

我正在尝试为长距离远足路径优化 Mapbox View ,例如阿巴拉契亚小径或太平洋山脊小径。下面是一个示例,我手动调整了方向,展示了西类牙的 Senda Pirenáica:

screen capture

给出感兴趣的区域、视口(viewport)和间距。我需要找到正确的中心、方位和缩放。

map.fitBounds 方法在这里对我没有帮助,因为它假设 pitch=0 和 bearing=0。

我查了一下,这似乎是 smallest surrounding rectangle 的变体。问题,但我遇到了一些额外的并发症:

  1. 如何解释音高的扭曲效应?
  2. 如何针对视口(viewport)的纵横比进行优化?请注意,使视口(viewport)变窄或变宽会改变最佳解决方案的方位:

sketch

FWIW 我也在使用 turf-js,它帮助我获得直线的凸包。

最佳答案

此解决方案导致在正确方位处显示的路径带有品红色梯形轮廓,显示目标“最紧梯形”以显示计算结果。来自顶 Angular 的额外线条显示了 map.center() 值所在的位置。

方法如下:

  1. 使用“fitbounds”技术渲染 map 路径以获得“北上和俯仰=0”情况的近似缩放级别
  2. 将音高旋转到所需的 Angular
  3. 从 Canvas 上抓取梯形

这个结果看起来像这样:

Initial view trapezoid

在此之后,我们想要围绕路径旋转梯形并找到梯形与点的最紧密拟合。为了测试最紧密的配合,旋转路径比旋转梯形更容易,所以我在这里采用了这种方法。我还没有在路径上实现“凸包”来最小化要旋转的点数,但这可以作为优化步骤添加。
为了获得最紧密的配合,第一步是移动 map.center() 以便路径位于 View 的“背面”。这是截锥体中空间最大的地方,因此很容易在那里操作它:

The yellow shows the adjusted view position, putting the path at the back of the view

接下来,我们测量 Angular 梯形墙与路径中每个点之间的距离,保存左右两侧最近的点。然后,我们根据这些距离水平平移 View ,使路径在 View 中居中,然后缩放 View 以消除两侧的空间,如下面的绿色梯形所示:

Green trapezoid shows the smallest fit

用于获得此“最紧密匹配”的比例为我们提供了关于这是否是最佳路径 View 的排名。然而,这个 View 在视觉上可能不是最好的,因为我们将路径推到 View 的后面来确定排名。相反,我们现在调整 View 以将路径放置在 View 的垂直中心,并相应地放大 View 三 Angular 形。这为我们提供了所需的洋红色“最终” View :

Final view in magenta.

最后,这个过程对每一度都完成,最小比例值决定获胜方位,我们从那里获取相关的比例和中心位置。

mapboxgl.accessToken = 'pk.eyJ1IjoiZm1hY2RlZSIsImEiOiJjajJlNWMxenowNXU2MzNudmkzMndwaGI3In0.ALOYWlvpYXnlcH6sCR9MJg';

var map;

var myPath = [
[-122.48369693756104, 37.83381888486939],
[-122.48348236083984, 37.83317489144141],
[-122.48339653015138, 37.83270036637107],
[-122.48356819152832, 37.832056363179625],
[-122.48404026031496, 37.83114119107971],
[-122.48404026031496, 37.83049717427869],
[-122.48348236083984, 37.829920943955045],
[-122.48356819152832, 37.82954808664175],
[-122.48507022857666, 37.82944639795659],
[-122.48610019683838, 37.82880236636284],
[-122.48695850372314, 37.82931081282506],
[-122.48700141906738, 37.83080223556934],
[-122.48751640319824, 37.83168351665737],
[-122.48803138732912, 37.832158048267786],
[-122.48888969421387, 37.83297152392784],
[-122.48987674713133, 37.83263257682617],
[-122.49043464660643, 37.832937629287755],
[-122.49125003814696, 37.832429207817725],
[-122.49163627624512, 37.832564787218985],
[-122.49223709106445, 37.83337825839438],
[-122.49378204345702, 37.83368330777276]
];

var myPath2 = [
[-122.48369693756104, 37.83381888486939],
[-122.49378204345702, 37.83368330777276]
];

function addLayerToMap(name, points, color, width) {
map.addLayer({
"id": name,
"type": "line",
"source": {
"type": "geojson",
"data": {
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": points
}
}
},
"layout": {
"line-join": "round",
"line-cap": "round"
},
"paint": {
"line-color": color,
"line-width": width
}
});
}
function Mercator2ll(mercX, mercY) {
var rMajor = 6378137; //Equatorial Radius, WGS84
var shift = Math.PI * rMajor;
var lon = mercX / shift * 180.0;
var lat = mercY / shift * 180.0;
lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0);

return [ lon, lat ];
}

function ll2Mercator(lon, lat) {
var rMajor = 6378137; //Equatorial Radius, WGS84
var shift = Math.PI * rMajor;
var x = lon * shift / 180;
var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
y = y * shift / 180;

return [ x, y ];
}

function convertLL2Mercator(points) {
var m_points = [];
for(var i=0;i<points.length;i++) {
m_points[i] = ll2Mercator( points[i][0], points[i][1] );
}
return m_points;
}
function convertMercator2LL(m_points) {
var points = [];
for(var i=0;i<m_points.length;i++) {
points[i] = Mercator2ll( m_points[i][0], m_points[i][1] );;
}
return points;
}
function pointsTranslate(points,xoff,yoff) {
var newpoints = [];
for(var i=0;i<points.length;i++) {
newpoints[i] = [ points[i][0] + xoff, points[i][1] + yoff ];
}
return(newpoints);
}

// note [0] elements are lng [1] are lat
function getBoundingBox(arr) {
var ne = [ arr[0][0] , arr[0][1] ];
var sw = [ arr[0][0] , arr[0][1] ];
for(var i=1;i<arr.length;i++) {
if(ne[0] < arr[i][0]) ne[0] = arr[i][0];
if(ne[1] < arr[i][1]) ne[1] = arr[i][1];
if(sw[0] > arr[i][0]) sw[0] = arr[i][0];
if(sw[1] > arr[i][1]) sw[1] = arr[i][1];
}
return( [ sw, ne ] );
}

function pointsRotate(points, cx, cy, angle){
var radians = angle * Math.PI / 180.0;
var cos = Math.cos(radians);
var sin = Math.sin(radians);
var newpoints = [];

function rotate(x, y) {
var nx = cx + (cos * (x - cx)) + (-sin * (y - cy));
var ny = cy + (cos * (y - cy)) + (sin * (x - cx));
return [nx, ny];
}
for(var i=0;i<points.length;i++) {
newpoints[i] = rotate(points[i][0],points[i][1]);
}
return(newpoints);
}

function convertTrapezoidToPath(trap) {
return([
[trap.Tl.lng, trap.Tl.lat], [trap.Tr.lng, trap.Tr.lat],
[trap.Br.lng, trap.Br.lat], [trap.Bl.lng, trap.Bl.lat],
[trap.Tl.lng, trap.Tl.lat] ]);
}

function getViewTrapezoid() {
var canvas = map.getCanvas();
var trap = {};

trap.Tl = map.unproject([0,0]);
trap.Tr = map.unproject([canvas.offsetWidth,0]);
trap.Br = map.unproject([canvas.offsetWidth,canvas.offsetHeight]);
trap.Bl = map.unproject([0,canvas.offsetHeight]);

return(trap);
}

function pointsScale(points,cx,cy, scale) {
var newpoints = []

for(var i=0;i<points.length;i++) {
newpoints[i] = [ cx + (points[i][0]-cx)*scale, cy + (points[i][1]-cy)*scale ];
}
return(newpoints);
}

var id = 1000;
function convertMercator2LLAndDraw(m_points, color, thickness) {
var newpoints = convertMercator2LL(m_points);
addLayerToMap("id"+id++, newpoints, color, thickness);
}

function pointsInTrapezoid(points,yt,yb,xtl,xtr,xbl,xbr) {
var str = "";
var xleft = xtr;
var xright = xtl;

var yh = yt-yb;
var sloperight = (xtr-xbr)/yh;
var slopeleft = (xbl-xtl)/yh;

var flag = true;

var leftdiff = xtr - xtl;
var rightdiff = xtl - xtr;

var tmp = [ [xtl, yt], [xtr, yt], [xbr,yb], [xbl,yb], [xtl,yt] ];
// convertMercator2LLAndDraw(tmp, '#ff0', 2);

function pointInTrapezoid(x,y) {
var xsloperight = xbr + sloperight * (y-yb);
var xslopeleft = xbl - slopeleft * (y-yb);

if((x - xsloperight) > rightdiff) {
rightdiff = x - xsloperight;
xright = x;
}
if((x - xslopeleft) < leftdiff) {
leftdiff = x - xslopeleft;
xleft = x;
}

if( (y<yb) || (y > yt) ) {
console.log("y issue");
}
else if(xsloperight < x) {
console.log("sloperight");
}
else if(xslopeleft > x) {
console.log("slopeleft");
}
else return(true);
return(false);
}

for(var i=0;i<points.length;i++) {
if(pointInTrapezoid(points[i][0],points[i][1])) {
str += "1";
}
else {
str += "0";
flag = false;
}
}
if(flag == false) console.log(str);

return({ leftdiff: leftdiff, rightdiff: rightdiff });
}

var viewcnt = 0;
function calculateView(trap, points, center) {
var bbox = getBoundingBox(points);
var bbox_height = Math.abs(bbox[0][1] - bbox[1][1]);
var view = {};

// move the view trapezoid so the path is at the far edge of the view
var viewTop = trap[0][1];
var pointsTop = bbox[1][1];
var yoff = -(viewTop - pointsTop);

var extents = pointsInTrapezoid(points,trap[0][1]+yoff,trap[3][1]+yoff,trap[0][0],trap[1][0],trap[3][0],trap[2][0]);

// center the view trapezoid horizontally around the path
var mid = (extents.leftdiff - extents.rightdiff) / 2;

var trap2 = pointsTranslate(trap,extents.leftdiff-mid,yoff);

view.cx = trap2[5][0];
view.cy = trap2[5][1];

var w = trap[1][0] - trap[0][0];
var h = trap[1][1] - trap[3][1];

// calculate the scale to fit the trapezoid to the path
view.scale = (w-mid*2)/w;

if(bbox_height > h*view.scale) {
// if the path is taller than the trapezoid then we need to make it larger
view.scale = bbox_height / h;
}
view.ranking = view.scale;

var trap3 = pointsScale(trap2,(trap2[0][0]+trap2[1][0])/2,trap2[0][1],view.scale);

w = trap3[1][0] - trap3[0][0];
h = trap3[1][1] - trap3[3][1];
view.cx = trap3[5][0];
view.cy = trap3[5][1];

// if the path is not as tall as the view then we should center it vertically for the best looking result
// this involves both a scale and a translate
if(h > bbox_height) {
var space = h - bbox_height;
var scale_mul = (h+space)/h;
view.scale = scale_mul * view.scale;
cy_offset = space/2;

trap3 = pointsScale(trap3,view.cx,view.cy,scale_mul);
trap3 = pointsTranslate(trap3,0,cy_offset);
view.cy = trap3[5][1];
}

return(view);
}

function thenCalculateOptimalView(path) {
var center = map.getCenter();
var trapezoid = getViewTrapezoid();
var trapezoid_path = convertTrapezoidToPath(trapezoid);
trapezoid_path[5] = [center.lng, center.lat];

var view = {};
//addLayerToMap("start", trapezoid_path, '#00F', 2);

// get the mercator versions of the points so that we can use them for rotations
var m_center = ll2Mercator(center.lng,center.lat);
var m_path = convertLL2Mercator(path);
var m_trapezoid_path = convertLL2Mercator(trapezoid_path);

// try all angles to see which fits best
for(var angle=0;angle<360;angle+=1) {
var m_newpoints = pointsRotate(m_path, m_center[0], m_center[1], angle);
var thisview = calculateView(m_trapezoid_path, m_newpoints, m_center);
if(!view.hasOwnProperty('ranking') || (view.ranking > thisview.ranking)) {
view.scale = thisview.scale;
view.cx = thisview.cx;
view.cy = thisview.cy;
view.angle = angle;
view.ranking = thisview.ranking;
}
}

// need the distance for the (cx, cy) from the current north up position
var cx_offset = view.cx - m_center[0];
var cy_offset = view.cy - m_center[1];
var rotated_offset = pointsRotate([[cx_offset,cy_offset]],0,0,-view.angle);

map.flyTo({ bearing: view.angle, speed:0.00001 });

// once bearing is set, adjust to tightest fit
waitForMapMoveCompletion(function () {
var center2 = map.getCenter();
var m_center2 = ll2Mercator(center2.lng,center2.lat);
m_center2[0] += rotated_offset[0][0];
m_center2[1] += rotated_offset[0][1];
var ll_center2 = Mercator2ll(m_center2[0],m_center2[1]);
map.easeTo({
center:[ll_center2[0],ll_center2[1]],
zoom : map.getZoom() });
console.log("bearing:"+view.angle+ " scale:"+view.scale+" center: ("+ll_center2[0]+","+ll_center2[1]+")");

// draw the tight fitting trapezoid for reference purposes
var m_trapR = pointsRotate(m_trapezoid_path,m_center[0],m_center[1],-view.angle);
var m_trapRS = pointsScale(m_trapR,m_center[0],m_center[1],view.scale);
var m_trapRST = pointsTranslate(m_trapRS,m_center2[0]-m_center[0],m_center2[1]-m_center[1]);
convertMercator2LLAndDraw(m_trapRST,'#f0f',4);
});
}

function waitForMapMoveCompletion(func) {
if(map.isMoving())
setTimeout(function() { waitForMapMoveCompletion(func); },250);
else
func();
}

function thenSetPitch(path,pitch) {
map.flyTo({ pitch:pitch } );
waitForMapMoveCompletion(function() { thenCalculateOptimalView(path); })
}

function displayFittedView(path,pitch) {
var bbox = getBoundingBox(path);
var path_cx = (bbox[0][0]+bbox[1][0])/2;
var path_cy = (bbox[0][1]+bbox[1][1])/2;

// start with a 'north up' view
map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [path_cx, path_cy],
zoom: 12
});

// use the bounding box to get into the right zoom range
map.on('load', function () {
addLayerToMap("path",path,'#888',8);
map.fitBounds(bbox);
waitForMapMoveCompletion(function() { thenSetPitch(path,pitch); });
});
}

window.onload = function(e) {
displayFittedView(myPath,60);
}
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.37.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.37.0/mapbox-gl.css' rel='stylesheet' />
<div id='map'></div>

关于javascript - mapbox-gl-js:针对给定间距将可见区域和方位调整到给定线,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43713462/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com