Trim interpolated polyline where terrain is intersecting in Cesium - javascript

I'm trying to create an arc which will stop when it intersects terrain. Two variables arc1 and arc2 where arc1 is without terrain intersection and arc2 is with terrain intersection.
arc1 is working properly but arc2 is not stopping where it intersects the terrain.
var viewer = new Cesium.Viewer("cesiumContainer", {
infoBox: false, //Disable InfoBox widget
selectionIndicator: false, //Disable selection indicator
shouldAnimate: true, // Enable animations
terrainProvider: Cesium.createWorldTerrain(),
});
//Enable lighting based on the sun position
viewer.scene.globe.enableLighting = true;
//Enable depth testing so things behind the terrain disappear.
viewer.scene.globe.depthTestAgainstTerrain = true;
// Cartesian3: start, mid and end points for polyline
var controls = [{x:-1942111.383043348,y:-4780646.881480431,z:3737538.9114379627},
{x:-1847333.0834675943,y:-5281519.814050422,z:3579120.5547780637},
{x:-1611854.2607850158,y:-5380130.367796415,z:3146339.4631628506}];
// Applying interpolation
var spline = Cesium.HermiteSpline.createNaturalCubic({
times: [0.0, 0.5, 1],
points: controls,
});
var ppos = []; //Interpolated polyline cordinates
for (var i = 0; i <= 50; i++) {
var cartesian3 = spline.evaluate(i / 50);
ppos.push(cartesian3);
}
/* Terrain and Polyline Intersection Detection */
var cartographic = [];
var cartographic_real = [];
ppos.forEach(p => {
let dh = Cesium.Cartographic.fromCartesian(p);
dh.height = 0;
cartographic.push(dh);
cartographic_real.push(Cesium.Cartographic.fromCartesian(p));
});
// collecting actual terrain heights
Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, cartographic).then((raisedPositionsCartograhpic) => {
var t = true;
var rpos = []; //
cartographic_real.forEach((p, i) => {
if(t) rpos.push(ppos[i]);
// If terrain height is more than polyline cordinate height
if (i > 0 && t && (raisedPositionsCartograhpic[i].height - p.height)) {
t = false;
console.log("Stopped at:", p.height);
console.log(rpos);
drawNewArc(rpos);
}
});
});
/* --- */
function drawNewArc(rpos)
{
var arc1 = {};
arc1.polyline = { // Actual line without terrain intersection
positions: ppos,
width: 3,
material: Cesium.Color.RED.withAlpha(0.5),
}
var arc2 = {};
arc2.polyline = { // This should stop where it intersects terrain
positions: rpos,
width: 3,
material: Cesium.Color.GREEN.withAlpha(1.0),
}
var entity = viewer.entities.add(arc1);
var entity2 = viewer.entities.add(arc2);
}
viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(-112.081,36.097,10000)});
I want the Green polyline to stop when it hits any terrain.
example
I think I need to get and match all the points of the arc. I don't know how to get all the points of the Green arc.
Here is the complete code link in sandcastle:

Got Success after repairing various mistakes in the code like:
Polyline segmentation was incorrect:
for (var i = 0; i <= 50; i++) {
Height matching condition was not correct:
if (i > 0 && t && (raisedPositionsCartograhpic[i].height - p.height)) {
Here is the complete working code link . It can also have mistakes and there must be a better solution to achieve the objective. If someone has any better and fast solution, please kindly post here.

Related

How can I straight cut a polygon into several pieces?

I want to develop a game "Cut the Shape" in JavaScript. Several components with convex polygons that will need to be clipped depending on the number of convex freedom faces. Lines and polygons are displayed on the Canvas. The Paper.js graphics load to use.
But then the question arose, which concerns the correct separation of the polygons from each other relative to the drawn line, I just can’t think of a way to do this.
Here is an example on simple polygons (the lines can be absolutely any, the user draws them himself):
I got to the points of dividing the polygon into other polygons using small dots with a line:
var polygon = new Path.RegularPolygon(new Point(200, 300), 4, 100);
polygon.strokeColor = 'blue';
polygon.fullySelected = true;
var shapesArray = [];
shapesArray.push(polygon);
function splitShape(path1, path2){
var shapesArrayCopy = path1.slice(0);
shapesArray = [];
for(var i = 0; i < shapesArrayCopy.length; i++){
var intersections = shapesArrayCopy[i].getIntersections(path2);
if(intersections.length >= 2){
var p1 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[0].point));
var p2 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[1].point));
p1.closed = true;
p2.closed = true;
shapesArray.push(Object.assign(p1));
shapesArray.push(Object.assign(p2));
path2.visible = false;
}
else{
shapesArray.push(shapesArrayCopy[i])
}
}
var myPath;
function onMouseDown(event) {
myPath = new Path();
myPath.strokeColor = 'black';
myPath.add(event.point);
myPath.add(event.point);
}
function onMouseDrag(event) {
myPath.segments.pop();
myPath.add(event.point);
}
function onMouseUp(event) {
splitShape(shapesArray, myPath)
myPath.visible = false;
}
In 2D You just displace the cuts in perpendicular direction to the cut line. If your cut line endpoints are: p0(x0,y0),p1(x1,y1) then the line direction is:
dp = p1-p0 = (x1-x0,y1-y0)
make it unit:
dp /= sqrt((dp.x*dp.x)+(dp.y*dp.y)
make it equal to half of the gap between cuts:
dp *= 0.5*gap
now tere are two perpendicular directions:
d0 = (-dp.y,+dp.x)
d1 = (+dp.y,-dp.x)
so now just add d0 to all vertexes of one cut, and d1 to the other one. Which use for which is simply you take point that does not lie on the cutting line (for example avg point of your cut) p and compute (only once for polygon cut):
t = dot(p-p0,d0) = ((p.x-x0)*d0.x)+((p.y-y0)*d0.y)
if (t>0) use d0, if (t<0) use d1 and if (t==0) you chose wrong point p as it lies on cutting line.

How to create this Canvas Animation JS

Does someone know how is this animation build, which js framework is used or something that can help me get to know to recreate something similar?
Banner BG animation ---> https://envylabs.com/
Thanks in advance!
I can't tell with what library this animation was build with, because it is hidden in React bundled code, but I can show you a way to do something similar with Paper.js.
By looking at the animation, it seems that rules are:
a circle is influenced by mouse pointer if it is under a certain distance from it
the closest to the mouse pointer a circle is:
the bigger it becomes
the farther from the window center it goes.
Here is a Sketch implementing this.
//
// CONSTANTS
//
// user defined
var ROWS_COUNT = 10; // number of rows in the grid
var COLUMNS_COUNT = 10; // number of columns in the grid
var MOUSE_INFLUENCE_RADIUS = 350; // maximal distance from mouse pointer to be influenced
var INFLUENCE_SCALE_FACTOR = 1; // maximal influence on point scale
var INFLUENCE_POSITION_FACTOR = 15; // maximal influence on point position
// computed
var STEP_X = view.bounds.width / COLUMNS_COUNT;
var STEP_Y = view.bounds.height / ROWS_COUNT;
var RADIUS = Math.min(STEP_X, STEP_Y) * 0.1;
//
// ITEMS
//
// create a circle for each points in the grid
var circles = [];
for (var i = 0; i < COLUMNS_COUNT; i++)
{
for (var j = 0; j < COLUMNS_COUNT; j++)
{
var gridPoint = new Point((i + 0.5) * STEP_X, (j + 0.5) * STEP_Y);
circles.push(new Path.Circle({
center : gridPoint,
radius : RADIUS,
fillColor : 'black',
// matrix application is disabled in order to be able to manipulate scaling property
applyMatrix: false,
// store original center point as item custom data property
data : {gridPoint: gridPoint}
}));
}
}
//
// EVENTS
//
function onMouseMove(event)
{
for (var i = 0; i < circles.length; i++)
{
var circle = circles[ i ];
var gridPoint = circle.data.gridPoint;
var distance = event.point.getDistance(gridPoint);
// only influence circles that are in mouse influence zone
if (distance <= MOUSE_INFLUENCE_RADIUS)
{
var influence = 1 - distance / MOUSE_INFLUENCE_RADIUS;
// the closest the circle is from the mouse pointer
// the bigger it is
circle.scaling = 1 + influence * INFLUENCE_SCALE_FACTOR;
// the farthest it is from view center
circle.position = gridPoint + (gridPoint - view.center).normalize(influence * INFLUENCE_POSITION_FACTOR);
}
else
{
// reset circle state
circle.scaling = 1;
circle.position = gridPoint;
}
}
}
Your question is too open.
But there are some libraries that will save you a lot of time:
D3.js
Processing.js
Paper.js
There are a lot more, but it depends on what you need.

Dynamically generated moveable vector shapes in Paper.js

I'm trying to render an arrow type shape in Paper.js. I have been able to create the segments that render out the tip of the arrow, but have been unable to create any further points that would finish the outline of the arrow. For my own testing purposes, it's currently only 3 lines, however I need to create a shape that can have fills, etc, so I need to be able to outline the arrow and have the group move dynamically when the mouse is dragged in a direction. I need a fat arrow!
Every point I choose, despite being relative to the position of the current vector, seem to rotate on their own when the arrow is manipulated.
Been hitting my head against this for days with no luck.
Here's what I'm working with -
var vectorStart, vector;
var vectorItem = new Group();
onMouseDrag = function (event) {
var arrowLength = 50;
vectorItem.remove();
engaged = true;
vectorStart = view.center;
var end = vectorStart + vector;
vector = event.point - vectorStart;
console.log('arrow pointer location: ' + event.point);
var vectorArrow = vector.normalize(arrowLength);
vectorItem = new Group([
new Path([vectorStart, end]),
new Path([
end + vectorArrow.rotate(120),
end,
end + vectorArrow.rotate(-120),
]),
]);
vectorItem.strokeWidth = 1;
vectorItem.strokeColor = 'black';
this.onMouseUp = function() {
vectorItem.remove();
}
}
Here's a link to a Sketch containing my code.
What I'm not understanding is how to add points to the Path that generates the arrow in order to create a shape. Everything seems to rotate on it's own and doesn't behave in the way that I need it to.
Any help would be great!
A simple way of drawing an arrow outline is by combining 3 rectangles.
Paper.js allow you to do this with Path.unite() method.
Here is an overview of the drawing algorithm
Here is a Sketch demonstrating my solution.
//
// CONSTANTS
//
// user defined
var STROKE_WIDTH = 40;
var HEAD_LENGTH = 300;
var STYLE = {
fillColor : 'orange',
strokeColor: 'black',
strokeWidth: 5
};
// computed
var WIDTH = STROKE_WIDTH * 2;
var DIAGONAL = Math.sqrt(Math.pow(STROKE_WIDTH * 2, 2) * 2);
//
// METHODS
//
/**
* Draws an arrow between two points.
* For simplicity sake, arrow is drawn horizontally at origin first
* then it is moved and rotated according to start / end points.
* It is composed of 3 rectangles which are united into a single shape.
* #param {Point} start
* #param {Point} end
*/
function drawArrow(start, end)
{
// calculate distance between points
var distance = start.getDistance(end);
// make sure it is not lower than diagonal
if (distance < DIAGONAL)
{
distance = DIAGONAL;
}
// draw rectangles
var directionRectangle = new Path.Rectangle(new Point(0, -STROKE_WIDTH), new Point(distance - DIAGONAL, STROKE_WIDTH));
var topRectangle = new Path.Rectangle(new Point(0, -STROKE_WIDTH), new Point(HEAD_LENGTH, STROKE_WIDTH));
// move top rectangle to the right
topRectangle.translate(directionRectangle.bounds.rightCenter - topRectangle.bounds.rightCenter + [ WIDTH, 0 ]);
// make bottom rectangle by cloning top one
var bottomRectangle = topRectangle.clone();
// offset top and bottom rectangles
topRectangle.position -= [ 0, STROKE_WIDTH ];
bottomRectangle.position += [ 0, STROKE_WIDTH ];
// rotate then to form arrow head
topRectangle.rotate(45, topRectangle.bounds.bottomRight - [ WIDTH, 0 ]);
bottomRectangle.rotate(-45, bottomRectangle.bounds.topRight - [ WIDTH, 0 ]);
// join the 3 rectangles into one path
var arrow = directionRectangle.unite(topRectangle).unite(bottomRectangle);
// move and rotate this path to fit start / end positions
arrow.translate(start - directionRectangle.bounds.leftCenter);
arrow.rotate((end - start).angle, start);
// apply custom styling
arrow.style = STYLE;
// remove construction items
directionRectangle.remove();
topRectangle.remove();
bottomRectangle.remove();
}
function onMouseDrag(event)
{
// clear canvas
project.clear();
// draw arrow according to mouse position
drawArrow(event.downPoint, event.point);
}
//
// INIT
//
// display instructions
new PointText({
point : view.center,
justification: 'center',
content : 'Draw arrow by dragging and dropping with your mouse.'
});
Here's some code that creates an arrow. The object is initialized with mouse down point and draws an arrow with the tip at the point the mouse is dragged to.
function Arrow (mouseDownPoint) {
this.start = mouseDownPoint;
this.headLength = 20;
this.tailLength = 9;
this.headAngle = 35;
this.tailAngle = 110
}
Arrow.prototype.draw = function (point) {
var end = point;
var arrowVec = this.start.subtract(end);
// parameterize {headLength: 20, tailLength: 6, headAngle: 35, tailAngle: 110}
// construct the arrow
var arrowHead = arrowVec.normalize(this.headLength);
var arrowTail = arrowHead.normalize(this.tailLength);
var p3 = end; // arrow point
var p2 = end.add(arrowHead.rotate(-this.headAngle)); // leading arrow edge angle
var p4 = end.add(arrowHead.rotate(this.headAngle)); // ditto, other side
var p1 = p2.add(arrowTail.rotate(this.tailAngle)); // trailing arrow edge angle
var p5 = p4.add(arrowTail.rotate(-this.tailAngle)); // ditto
// specify all but the last segment, closed does that
this.path = new paper.Path(this.start, p1, p2, p3, p4, p5);
this.path.closed = true;
this.path.strokeWidth = 1
this.path.strokColor = 'black'
this.path.fillColor = 'black'
return this.path
}
I like the tapered tail but you can get rid of that by fiddling with the constructor lengths.
Here's a sketch with the mouse handling

mapbox-gl-js: Adjust visible area & bearing to a given line, for a given pitch

I'm trying to optimize a Mapbox view for long-distance hiking trails, like the Appalachian Trail or the Pacific Crest Trail. Here's an example, which I've oriented by hand, showing the Senda Pirenáica in Spain:
The area of interest, the viewport, and the pitch are given. I need to find the correct center, bearing, and zoom.
The map.fitBounds method doesn't help me here because it assumes pitch=0 and bearing=0.
I've done some poking around and this seems to be a variation of the smallest surrounding rectangle problem, but I'm stuck on a couple of additional complications:
How do I account for the distorting effect of pitch?
How do I optimize for the aspect ratio of the viewport? Note that taking the viewport narrower or wider would change the bearing of the best solution:
FWIW I'm also using turf-js, which helps me get the convex hull for the line.
This solution results in the path displayed at the correct bearing with a magenta trapezoid outline showing the target "tightest trapezoid" to show the results of the calculations. The extra line coming from the top corner shows where the map.center() value is located.
The approach is as follows:
render the path to the map using the "fitbounds" technique to get an approximate zoom level for the "north up and pitch=0" situation
rotate the pitch to the desired angle
grab the trapezoid from the canvas
This result would look like this:
After this, we want to rotate that trapezoid around the path and find the tightest fit of the trapezoid to the points. In order to test for the tightest fit it is easier to rotate the path rather than the trapezoid so I have taken that approach here. I haven't implemented a "convex hull" on the path to minimize the number of points to rotate but that is something that can be added as an optimization step.
To get the tightest fit, the first step is to move the map.center() so that the path is at the "back" of the view. This is where the most space is in the frustum so it will be easy to manipulate it there:
Next, we measure the distance between the angled trapezoid walls and each point in the path, saving the closest points on both the left and right sides. We then center the path in the view by translating the view horizontally based on these distances, and then scale the view to eliminate that space on both sides as shown by the green trapezoid below:
The scale used to get this "tightest fit" gives us our ranking for whether this is the best view of the path. However, this view may not be the best visually since we pushed the path to the back of the view to determine the ranking. Instead, we now adjust the view to place the path in the vertical center of the view, and scale the view triangle larger accordingly. This gives us the magenta colored "final" view desired:
Finally, this process is done for every degree and the minimum scale value determines the winning bearing, and we take the associated scale and center position from there.
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>
Smallest surrounding rectangle would be specific to pitch=0 (looking directly down).
One option is to continue with smallest surrounding rectangle approach and calculate the transformation of the target area - just like a 3d engine does. If this is what you do maybe skim through unity docs to better understand the mechanics of viewing frustum
I feel this wouldn't be appropriate for your problem though as you'd have to re-calculate a 2d rendering of the target area from different angles, a relatively expensive brute force.
Another way to normalize the calculation would be to render a viewport projection into target area plane. See for yourself:
Then all you have to do is "just" figure out the largest size your original convex hull can fit into a trapezoid of that shape (specifically a convex isosceles trapezoid since we don't manipulate camera roll).
This is where I get a little out of depth and don't know where to point you for a calculation. I figure it's at least cheaper to iterate over possible solutions in this 2D space though.
P.S: One more thing to keep in mind is the viewport projection shape will be different depending on FOV (field of view).
This changes when you resize the browser viewport, but the property doesn't seem to be exposed in mapbox-gl-js.
Edit:
After some thought I feel the best mathematical solution can feel a little "dry" in reality. Not being across the use case and, possibly, making some wrong assumptions, I'd ask these questions:
For a route that's roughly a straight line, would it always be panned in so the ends are at bottom left and top right corners? That would be close to "optimal" but could get... boring.
Would you want to keep more of the path closer to the viewport? You can lose route detail if a large portion of it is far away from the viewport.
Would you pick points of interest to focus on? Those could be closer to the viewport.
Perhaps it would be handy to classify different types of routes by shape of hull and create panning presets?
Hopefully this can point you in the right direction with some tweaking.
First I set up the two points we want to show
let pointA = [-70, 43]
let pointB = [-83, 32]
Then I found the middle of those two points. I made my own function for this, but it looks like turf can do this.
function middleCoord(a, b){
let x = (a - b)/2
return _.min([a, b]) + x
}
let center = [middleCoord(pointA[0], pointB[0]), middleCoord(pointA[1], pointB[1])]
I used turfs bearing function to have the view from the 2nd point look at the first point
let p1 = turf.point(pointA)
let p2 = turf.point(pointB)
let points = turf.featureCollection([p1, p2])
let bearing = turf.bearing(p2, p1)
Then I call the map and run the fitBounds function:
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/outdoors-v10', //hosted style id
center: center, // starting position
zoom: 4, // starting zoom
pitch: 60,
bearing: bearing
})
map.fitBounds([pointA, pointB], {padding: 0, offset: 0})
Here's a codepen: https://codepen.io/thejoshderocher/pen/BRYGXq
To adjust the bearing to best use the screen size is to get the size of the window and adjust the bearing to take the most advantage of the available screen space. If it's a mobile screen in portrait, this bearing work perfect. If you are on a desktop with a wide view you will need to rotate so point A is in one of the top corners.

ArcGIS Circle Buffer

I am stuck, trying to draw a simple circle buffer using ArcGIS. Here is how I set up my base map:
dojo.require("esri.map");
dojo.require("esri.tasks.servicearea");
dojo.require("dijit.dijit");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("esri/layers/FeatureLayer");
dojo.require("esri/symbols/SimpleMarkerSymbol");
var mapLayers = new Array();
var map;
var GL = null;
function setMap() {
function init() {
require(
[
"esri/map",
"dojo/dom-construct",
"dojo/domReady!",
"esri/tasks/GeometryService"
],
function
(
Map,
domConstruct
) {
map = Map("map-canvas",
{
//infoWindow: popup
});
map.setZoom(1);
coreFunctions();
});
}
dojo.ready(init);
dojo.connect(map, "onClick", addCircle);
}
function coreFunctions() {
try {
addLayersToMap();
}
catch (err) {
console.log(err);
}
}
function addLayersToData() {
var layer = new esri.layers.ArcGISTiledMapServiceLayer("https://www.onemap.sg/ArcGIS/rest/services/BASEMAP/MapServer");
mapLayers.push(layer);
var layer2 = new esri.layers.ArcGISTiledMapServiceLayer("http://www.onemap.sg/ArcGIS/rest/services/LOT_VIEW/MapServer");
mapLayers.push(layer2);
layer2.hide();
}
function addLayersToMap() {
addLayersToData();
for (var a = 0; a < mapLayers.length; a++) {
map.addLayer(mapLayers[a]);
}
map.setZoom(1);
}
So here is another method where I try to draw a circle graphic on the map:
function addCircle(e) {
sym = new esri.symbol.SimpleFillSymbol().setColor(new dojo.Color([180, 0, 180, 1.0]));
console.log("clicked the map: ", e);
var pt, radius, circle, ring, pts, angle;
pt = e.mapPoint;
circle = new esri.geometry.Polygon(map.spatialReference);
ring = []; // point that make up the circle
pts = 40; // number of points on the circle
angle = 360 / pts; // used to compute points on the circle
for (var i = 1; i <= pts; i++) {
// convert angle to raidans
var radians = i * angle * Math.PI / 180;;
// add point to the circle
ring.push([pt.x + radius * Math.cos(radians), pt.y + radius * Math.sin(radians)]);
console.log(ring[i]);
}
ring.push(ring[0]); // start point needs to == end point
circle.addRing(ring);
map.graphics.add(new esri.Graphic(circle, sym));
console.log("added a graphic");
}
It did execute the function but there is not circle plotted on the map and as well as error message. I wonder which part of my logic went wrong?
Thanks in advance.
At API version 3.8 there is already a class for this task https://developers.arcgis.com/javascript/jsapi/circle-amd.html with this class you can even draw geodesic circles.
You're very, very close - the only problem is you're never setting the value of your variable radius, so all your coordinates are being calculated as pt.x + (nothing) * Math.cos(radians)....which in javascript is NaN. I added radius = 100; before your for loop and everything works nicely.
This is actually a pretty easy error to debug. IMO if you're not using Chrome for ESRI JS development, then you should be - its inbuilt dev tools are excellent for examining object properties. The way I worked out what was going on was simply to dump the map.graphics.graphics array to the console - you can then drill down into the geometry.rings of each array element and very quickly work out that each point has x = NaN and y = NaN, and from there it's obvious that there's something wrong with your calculation.

Categories

Resources