How to make a zoom between two marker in maps - flutter

i try to make a zoom in two markers in maps with flutter but i didn't find a method in flutter fitBounds.
getRouteCoordinates(_initialPosition, destination);
*LatLngBounds bound = LatLngBounds(northeast: initialPosition,southwest: destination);
//mapController.getVisibleRegion();
CameraUpdate u2 = CameraUpdate.newLatLngBounds(bound, 10);
this.mapController.animateCamera(u2).then((void v){
check(u2,this.mapController);
});
void check(CameraUpdate u, GoogleMapController c) async {
c.animateCamera(u);
mapController.animateCamera(u);
LatLngBounds l1=await c.getVisibleRegion();
LatLngBounds l2=await c.getVisibleRegion();
print(l1.toString());
print(l2.toString());
if(l1.southwest.latitude==-90 ||l2.southwest.latitude==-90)
check(u, c);
}

Firstly ,make a list from your GeoPoint,and make a function to choose the Point of Bounds.
LatLngBounds boundsFromLatLngList(List<LatLng> list) {
assert(list.isNotEmpty);
double x0, x1, y0, y1;
for (LatLng latLng in list) {
if (x0 == null) {
x0 = x1 = latLng.latitude;
y0 = y1 = latLng.longitude;
} else {
if (latLng.latitude > x1) x1 = latLng.latitude;
if (latLng.latitude < x0) x0 = latLng.latitude;
if (latLng.longitude > y1) y1 = latLng.longitude;
if (latLng.longitude < y0) y0 = latLng.longitude;
}
}
return LatLngBounds(northeast: LatLng(x1, y1), southwest: LatLng(x0, y0));
}
After That Call this function and make zoom
LatLngBounds bound = boundsFromLatLngList(listmap);
await Future.delayed(Duration(milliseconds:500)).then((v) async {
CameraUpdate u2 = CameraUpdate.newLatLngBounds(bound, 50);
this.mapController.animateCamera(u2).then((void v) {
check(u2, this.mapController);
});
});
This snippet for code is work for me

Related

How to remove lag from live camera while implementing face recognition app in flutter?

how to remove lags while using real time face recognition in flutter?
here i tried with google ml kit and tensorflow lite ,mobile facenet .
This is the camera function...
`void initialCamera() async {
loadModel();
final InputImageRotation? imageRotation =
InputImageRotationValue.fromRawValue(description.sensorOrientation);
_camera = CameraController(description, ResolutionPreset.medium,
enableAudio: false);
await _camera!.initialize();
tempDir = await getApplicationDocumentsDirectory();
String _embPath = tempDir!.path + '/emb.json';
jsonFile = new File(_embPath);
if (jsonFile!.existsSync())
data = json.decode(jsonFile!.readAsStringSync());
_camera!.startImageStream((CameraImage image) async {
if (_camera != null) {
if (_isDetecting) return;
_isDetecting = true;
String res_name;
dynamic finalResult = Multimap<String, Face>();
List<Face> faces = await detect(image, imageRotation!);
if (faces.length == 0)
_faceFound = false;
else
_faceFound = true;
Face _face;
imglib.Image convertedImage = _convertCameraImage(image, _direction);
for (_face in faces) {
double x, y, w, h;
x = (_face.boundingBox.left - 10);
y = (_face.boundingBox.top - 10);
w = (_face.boundingBox.width + 10);
h = (_face.boundingBox.height + 10);
imglib.Image croppedImage = imglib.copyCrop(
convertedImage,
x.round(),
y.round(),
w.round(),
h.round(),
);
croppedImage = imglib.copyResizeCropSquare(croppedImage, 112);
res_name = _recog(croppedImage);
finalResult.add(res_name, _face);
}
setState(() {
_scanResults = finalResult;
});
_isDetecting = false;
}
});
}`

Flutter LatLngBounds not showing accurate place

I am trying to show all of my markers into the viewport using my flutter google maps. But it seems not working in my case. I have tried so far as below:
_controller.animateCamera(CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(23.785182, 90.330702),
northeast: LatLng(24.582782, 88.821163),
),
100
));
LatLngBounds boundsFromLatLngList(List<LatLng> list) {
assert(list.isNotEmpty);
double x0, x1, y0, y1;
for (LatLng latLng in list) {
if (x0 == null) {
x0 = x1 = latLng.latitude;
y0 = y1 = latLng.longitude;
} else {
if (latLng.latitude > x1) x1 = latLng.latitude;
if (latLng.latitude < x0) x0 = latLng.latitude;
if (latLng.longitude > y1) y1 = latLng.longitude;
if (latLng.longitude < y0) y0 = latLng.longitude;
}
}
return LatLngBounds(northeast: LatLng(x1, y1), southwest: LatLng(x0, y0));
}
As i have seen, it just always show the map of North Atlantic Ocean
Is there any solution regarding this issue or it is just under development in Flutter ?. thanks in advance
I'm facing the exact same issue on Android (works fine on iOS) when I animate the map with CameraUpdate.newLatLngBounds. It repositions to North Pacific Ocean immediately after setting the bounds, not sure what's causing this but here's a workaround -
Instead of setting the map position using LatLngBounds, you can calculate the centre of the bounds you want to set
// the bounds you want to set
LatLngBounds bounds = LatLngBounds(
southwest: LatLng(23.785182, 90.330702),
northeast: LatLng(24.582782, 88.821163),
);
// calculating centre of the bounds
LatLng centerBounds = LatLng(
(bounds.northeast.latitude + bounds.southwest.latitude)/2,
(bounds.northeast.longitude + bounds.southwest.longitude)/2
);
// setting map position to centre to start with
controller.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: centerBounds,
zoom: 17,
)));
zoomToFit(controller, bounds, centerBounds);
Once you set the map position to the centre of the bounds (and zoomed in), you then need to keep zooming out till the visible map region covers the bounds you want to set. You can get the visible map region with controller.getVisibleRegion(). Here's the implementation -
Future<void> zoomToFit(GoogleMapController controller, LatLngBounds bounds, LatLng centerBounds) async {
bool keepZoomingOut = true;
while(keepZoomingOut) {
final LatLngBounds screenBounds = await controller.getVisibleRegion();
if(fits(bounds, screenBounds)){
keepZoomingOut = false;
final double zoomLevel = await controller.getZoomLevel() - 0.5;
controller.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: centerBounds,
zoom: zoomLevel,
)));
break;
}
else {
// Zooming out by 0.1 zoom level per iteration
final double zoomLevel = await controller.getZoomLevel() - 0.1;
controller.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: centerBounds,
zoom: zoomLevel,
)));
}
}
}
bool fits(LatLngBounds fitBounds, LatLngBounds screenBounds) {
final bool northEastLatitudeCheck = screenBounds.northeast.latitude >= fitBounds.northeast.latitude;
final bool northEastLongitudeCheck = screenBounds.northeast.longitude >= fitBounds.northeast.longitude;
final bool southWestLatitudeCheck = screenBounds.southwest.latitude <= fitBounds.southwest.latitude;
final bool southWestLongitudeCheck = screenBounds.southwest.longitude <= fitBounds.southwest.longitude;
return northEastLatitudeCheck && northEastLongitudeCheck && southWestLatitudeCheck && southWestLongitudeCheck;
}
Had the same issue the problem for me:
I didn't really gave the southwest and northeast coordinates but:
NorthWest and SouthEast. iOS handled this normally but android zoomed on the Atlantic.
After fixing that like:
final highestLat = points.map((e) => e.latitude).reduce(max);
final highestLong = points.map((e) => e.longitude).reduce(max);
final lowestLat = points.map((e) => e.latitude).reduce(min);
final lowestLong = points.map((e) => e.longitude).reduce(min);
final lowestLatLowestLong = LatLng(lowestLat, lowestLong);
final highestLatHighestLong = LatLng(highestLat, highestLong);
final getRouteBoundsCameraUpdate = CameraUpdate.newLatLngBounds(LatLngBounds(southwest: lowestLatLowestLong, northeast: highestLatHighestLong), 25.0);
I also had this issue, try swapping the southwest value with northeast value
i found this way that work for me perfectly .
Future<void> updateCameraLocation(
LatLng source,
LatLng destination,
GoogleMapController mapController,
) async {
if (mapController == null) return;
LatLngBounds bounds;
if (source.latitude > destination.latitude &&
source.longitude > destination.longitude) {
bounds = LatLngBounds(southwest: destination, northeast: source);
} else if (source.longitude > destination.longitude) {
bounds = LatLngBounds(
southwest: LatLng(source.latitude, destination.longitude),
northeast: LatLng(destination.latitude, source.longitude));
} else if (source.latitude > destination.latitude) {
bounds = LatLngBounds(
southwest: LatLng(destination.latitude, source.longitude),
northeast: LatLng(source.latitude, destination.longitude));
} else {
bounds = LatLngBounds(southwest: source, northeast: destination);
}
CameraUpdate cameraUpdate = CameraUpdate.newLatLngBounds(bounds, 70);
return checkCameraLocation(cameraUpdate, mapController);
}
Future<void> checkCameraLocation(
CameraUpdate cameraUpdate, GoogleMapController mapController) async {
mapController.animateCamera(cameraUpdate);
LatLngBounds l1 = await mapController.getVisibleRegion();
LatLngBounds l2 = await mapController.getVisibleRegion();
if (l1.southwest.latitude == -90 || l2.southwest.latitude == -90) {
return checkCameraLocation(cameraUpdate, mapController);
}
}
And use by this line :
await updateCameraLocation(source, destination, controller);

Get two offset points between two points

Hi I need help finding coordinate or points offset from two endpoints of a line. In my program, I would like to specify the two points and the offset. Then I need to calculate the two offset coordinates.
I worked something out using trigonometry but it only works in some cases and when the line is in the positive quadrant.
Here is an image describing what I need to find:
Points on line
Ok so I need to find X3,Y3 and X4,Y4 coordinates.
My method I followed:
Calculate angle:
Ang = atan((Y2 - Y1)/(X2 - X1))
To find X3:
X3 = X1 + Offset * Cos(Ang)
The same concept for Y3
The issue is that if the line is in a different quadrant the point info is not correct... Any help, please.
This question is a clear case for using 2d vector math. The idea is that we subtract p1 from p2 to give us a vector that describes the length and direction of the line. We then normalize this vector, such that it has a length of 1. If you then multiply this normalized vector with the number of units you'd like to move away from the end and add the result to the end-point, you'll have a new point.
Consider an example walking along the x axis:
p1 = 0,0
p2 = 10,0
dif = p2 - p1 = (10,0)
length is 10, so it's 10 times too long - we divide it by 10 to get a vector 1 unit long.
If we then move 5 times (1,0), we end up at 5,0 - 5 units away, bewdy!
Here's a function that achieves the same thing:
function calcOffsetPoint(x1,y1, x2,y2, distTowardsP2fromP1)
{
var p1 = new vec2d(x1,y1);
var p2 = new vec2d(x2,y2);
var delta = p2.sub(p1);
var dirVec = delta.clone();
dirVec.normalize();
dirVec.timesEquals(distTowardsP2fromP1);
var resultPoint = p1.add(dirVec);
return resultPoint;
}
As you can see, this makes use of something I've called vec2d. There's a copy of it in the following snippet:
"use strict";
function byId(id){return document.getElemetById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
var end1 = new vec2d(0,0);
var end2 = new vec2d(10,0);
var midPoint = calcOffsetPoint(end1.x,end1.y, end2.x,end2.y, 5);
console.log( midPoint.toStringN(2) );
}
class vec2d
{
constructor(x=0, y=0)
{
this.mX = x;
this.mY = y;
}
get x(){return this.mX;}
set x(newX){this.mX = newX;}
get y(){return this.mY;}
set y(newY){this.mY = newY;}
add(other)
{
return new vec2d(this.x+other.x, this.y+other.y);
}
sub(other)
{
return new vec2d(this.x-other.x, this.y-other.y);
}
timesEquals(scalar)
{
this.x *= scalar;
this.y *= scalar;
return this;
}
divByEquals(scalar)
{
this.x /= scalar;
this.y /= scalar;
return this;
}
dotProd(other)
{
return this.x*other.x + this.y*other.y;
}
length()
{
return Math.hypot(this.x, this.y);
}
normalize()
{
this.divByEquals( this.length() );
return this;
}
perpendicular()
{
var tmp = this.x;
this.x = -this.y;
this.y = tmp;
return this;
}
clone()
{
return vec2d.clone(this);
}
static clone(other)
{
return new vec2d(other.x, other.y);
}
toString(){return `vec2d {x: ${this.x}, y: ${this.y}}`}
toStringN(n){return `vec2d {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}`}
}
function calcOffsetPoint(x1,y1, x2,y2, distTowardsP2fromP1)
{
var p1 = new vec2d(x1,y1);
var p2 = new vec2d(x2,y2);
var delta = p2.sub(p1);
var dirVec = delta.clone();
dirVec.normalize();
dirVec.timesEquals(distTowardsP2fromP1);
var resultPoint = p1.add(dirVec);
return resultPoint;
}
I had some spare time over the weekend, so put together a working demo of the image you posted. Have a play around. Make sure you run it in full-screen, so you can see the sliders that set the offsets for p3 and p4. Disregard the coordinate-system transformation stuff, that's just there to allow me to make an image the same dimensions as your image yet conveniently display it in a window with about 5% the area. The questions come from the exercise section of some old text-book I was reading over the weekend.
"use strict";
class vec2d
{
constructor(x=0,y=0)
{
this.x = x;
this.y = y;
}
abs()
{
this.x = Math.abs(this.x);
this.y = Math.abs(this.y);
return this;
}
add(vec1)
{
return new vec2d(this.x+vec1.x, this.y+vec1.y);
}
sub(vec1)
{
return new vec2d(this.x-vec1.x, this.y-vec1.y);
}
mul(scalar)
{
return new vec2d(this.x*scalar, this.y*scalar);
}
plusEquals(vec1)
{
this.x += vec1.x;
this.y += vec1.y;
return this;
}
minusEquals(vec1)
{
this.x -= vec1.x;
this.y -= vec1.y;
return this;
}
timesEquals(scalar)
{
this.x *= scalar;
this.y *= scalar;
return this;
}
divByEquals(scalar)
{
this.x /= scalar;
this.y /= scalar;
return this;
}
normalize()
{
var len = this.length;
this.x /= len;
this.y /= len;
return this;
}
get length()
{
//return Math.sqrt( (this.x*this.x)+(this.y*this.y) );
return Math.hypot( this.x, this.y );
}
set length(newLen)
{
var invLen = newLen / this.length;
this.timesEquals(invLen);
}
dotProd(vec1)
{
return this.x*vec1.x + this.y*vec1.y;
}
perp()
{
var tmp = this.x;
this.x = -this.y;
this.y = tmp;
return this;
}
wedge(other)
{ // computes an area for parallelograms
return this.x*other.y - this.y*other.x;
}
static clone(other)
{
var result = new vec2d(other.x, other.y);
return result;
}
clone() // clone self
{
return vec2d.clone(this);
}
setTo(other)
{
this.x = other.x;
this.y = other.y;
}
get(){ return {x:this.x, y:this.y}; }
toString(){ return `vec2d {x: ${this.x}, y: ${this.y}}` }
toStringN(n){ return `vec2d {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}` }
print(){console.log(this.toString())}
};
class mat3
{
static clone(other)
{
var result = new mat3();
other.elems.forEach(
function(el, index, collection)
{
result.elems[index] = el;
}
);
return result;
}
clone()
{
return mat3.clone(this);
}
constructor(a,b,c,d,e,f)
{
if (arguments.length < 6)
this.setIdentity();
else
this.elems = [a,b,0,c,d,0,e,f,1];
}
setIdentity()
{
this.elems = [1,0,0, 0,1,0, 0,0,1];
}
multiply(other, shouldPrepend)
{
var a, b, c = new mat3();
if (shouldPrepend === true)
{
a = other;
b = this;
}
else
{
a = this;
b = other;
}
c.elems[0] = a.elems[0]*b.elems[0] + a.elems[1]*b.elems[3] + a.elems[2]*b.elems[6];
c.elems[1] = a.elems[0]*b.elems[1] + a.elems[1]*b.elems[4] + a.elems[2]*b.elems[7];
c.elems[2] = a.elems[0]*b.elems[2] + a.elems[1]*b.elems[5] + a.elems[2]*b.elems[8];
// row 1
c.elems[3] = a.elems[3]*b.elems[0] + a.elems[4]*b.elems[3] + a.elems[5]*b.elems[6];
c.elems[4] = a.elems[3]*b.elems[1] + a.elems[4]*b.elems[4] + a.elems[5]*b.elems[7];
c.elems[5] = a.elems[3]*b.elems[2] + a.elems[4]*b.elems[5] + a.elems[5]*b.elems[8];
// row 2
c.elems[6] = a.elems[6]*b.elems[0] + a.elems[7]*b.elems[3] + a.elems[8]*b.elems[6];
c.elems[7] = a.elems[6]*b.elems[1] + a.elems[7]*b.elems[4] + a.elems[8]*b.elems[7];
c.elems[8] = a.elems[6]*b.elems[2] + a.elems[7]*b.elems[5] + a.elems[8]*b.elems[8];
for (var i=0; i<9; i++)
this.elems[i] = c.elems[i];
}
transformVec2s(pointList)
{
var i, n = pointList.length;
for (i=0; i<n; i++)
{
var x = pointList[i].x*this.elems[0] + pointList[i].y*this.elems[3] + this.elems[6];
var y = pointList[i].x*this.elems[1] + pointList[i].y*this.elems[4] + this.elems[7];
pointList[i].x = x;
pointList[i].y = y;
}
}
makeTransformedPoints(pointList)
{
var result = [];
for (var i=0,n=pointList.length;i<n;i++)
{
var x = pointList[i].x*this.elems[0] + pointList[i].y*this.elems[3] + this.elems[6];
var y = pointList[i].x*this.elems[1] + pointList[i].y*this.elems[4] + this.elems[7];
result.push( new vec2d(x,y) );
}
return result;
}
rotate(degrees, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = Math.cos( degrees/180.0 * Math.PI );
tmp.elems[1] = -Math.sin( degrees/180.0 * Math.PI );
tmp.elems[3] = -tmp.elems[1];
tmp.elems[4] = tmp.elems[0];
this.multiply(tmp, shouldPrepend);
}
scaleEach(scaleX, scaleY, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = scaleX;
tmp.elems[4] = scaleY;
this.multiply(tmp, shouldPrepend);
}
scaleBoth(scaleAmount, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = scaleAmount;
tmp.elems[4] = scaleAmount;
this.multiply(tmp, shouldPrepend);
}
translate(transX, transY, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[6] = transX;
tmp.elems[7] = transY;
this.multiply(tmp, shouldPrepend);
}
determinant()
{
var result, a, b;
a = ( (this.elems[0]*this.elems[4]*this.elems[8])
+ (this.elems[1]*this.elems[5]*this.elems[6])
+ (this.elems[2]*this.elems[3]*this.elems[7]) );
b = ( (this.elems[2]*this.elems[4]+this.elems[6])
+ (this.elems[1]*this.elems[3]+this.elems[8])
+ (this.elems[0]*this.elems[5]+this.elems[7]) );
result = a - b;
return result;
}
isInvertible()
{
return (this.determinant() != 0);
}
invert()
{
var det = this.determinant();
if (det == 0)
return;
var a,b,c,d,e,f,g,h,i;
a = this.elems[0]; b = this.elems[1]; c = this.elems[2];
d = this.elems[3]; e = this.elems[4]; f = this.elems[5];
g = this.elems[6]; h = this.elems[7]; i = this.elems[8];
this.elems[0] = (e*i - f*h); this.elems[1] = -((b*i) - (c*h)); this.elems[2] = (b*f)-(c*e);
this.elems[3] = -(d*i - f*g); this.elems[4] = (a*i) - (c*g); this.elems[5] = -( (a*f) - (c*d) );
this.elems[6] = (d*h - e*g); this.elems[7] = -((a*h) - (b*g)); this.elems[8] = (a*e)-(b*d);
var detInv = 1.0 / det;
for (var i=0; i<9; i++)
this.elems[i] *= detInv;
return this;
}
reset()
{
this.setIdentity();
}
print()
{
var str = '';
for (var i=0; i<9; i++)
{
if (i && i%3==0)
str += "\n";
str += " " + this.elems[i].toFixed(5);
}
console.log(str);
}
}
function byId(id){return document.getElementById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
byId('output').addEventListener('mousemove', onMouseMove, false);
byId('slider1').addEventListener('input', onSliderInput, false);
byId('slider2').addEventListener('input', onSliderInput, false);
draw();
}
//(400-48)/400 = 0.88
var invMat, svgInvMat;
function onMouseMove(evt)
{
var mousePos = new vec2d(evt.offsetX,evt.offsetY);
var worldPos = mousePos.clone();
invMat.transformVec2s( [worldPos] );
byId('screenMouse').textContent = `screen: ${mousePos.x},${mousePos.y}`;
byId('worldMouse').textContent = `world: ${worldPos.x.toFixed(1)}, ${worldPos.y.toFixed(1)}`;
}
function onSliderInput(evt)
{
draw();
}
function updateSliderLabels()
{
byId('ofset1Output').textContent = byId('slider1').value;
byId('ofset2Output').textContent = byId('slider2').value;
}
function draw()
{
var can = byId('output');
var ctx = can.getContext('2d');
ctx.clearRect(0,0,can.width,can.height);
var orientMat = evaluateViewOrientationMatrix(0.06*can.width,can.height-24, 0,-1);
var scaleMat = computeWindowToViewPortMatrix(2052,1317, can.width,can.height);
var viewMat = scaleMat.clone();
viewMat.multiply(orientMat);
console.log('viewMat');
viewMat.print();
invMat = viewMat.clone().invert();
for (var i=0; i<9; i++)
invMat.elems[i] /= invMat.elems[8];
ctx.strokeStyle = '#fff';
var axisPts = [ new vec2d(0,1070), new vec2d(0,0), new vec2d(0.88*2052,0) ]; // xAxis line 88% of image width
var axis = viewMat.makeTransformedPoints(axisPts);
drawLine(axis[0].x,axis[0].y, axis[1].x,axis[1].y, ctx);
drawLine(axis[1].x,axis[1].y, axis[2].x,axis[2].y, ctx);
var lineEnds = [new vec2d(330,263), new vec2d(1455,809)];
var pts2 = viewMat.makeTransformedPoints(lineEnds);
drawCircle(pts2[0].x,pts2[0].y, 4, ctx);
drawCircle(pts2[1].x,pts2[1].y, 4, ctx);
drawLine(pts2[0].x,pts2[0].y, pts2[1].x,pts2[1].y, ctx);
var rawP3 = calcOffsetCoords(lineEnds[0].x,lineEnds[0].y, lineEnds[1].x,lineEnds[1].y, byId('slider1').value);
var rawP4 = calcOffsetCoords(lineEnds[1].x,lineEnds[1].y, lineEnds[0].x,lineEnds[0].y, byId('slider2').value);
var ofsPts = viewMat.makeTransformedPoints( [rawP3, rawP4] );
drawCircle(ofsPts[0].x,ofsPts[0].y, 4, ctx);
drawCircle(ofsPts[1].x,ofsPts[1].y, 4, ctx);
updateSliderLabels();
}
function calcOffsetCoords(x1,y1, x2,y2, offset)
{
var dx = x2 - x1;
var dy = y2 - y1;
var lineLen = Math.hypot(dx, dy);
var normDx=0, normDy=0;
if (lineLen != 0)
{
normDx = dx / lineLen;
normDy = dy / lineLen;
}
var resultX = x1 + (offset * normDx);
var resultY = y1 + (offset * normDy);
return {x:resultX,y:resultY};//new vec2d(resultX,resultY); //{x:resultX,y:resultY};
}
// Exercise 6-1:
// Write a procedure to implement the evaluateViewOrientationMatrix function that calculates the elements of the
// matrix for transforming world coordinates to viewing coordinates, given the viewing coordinate origin Porigin and
// the viewUp vector
function evalViewOrientMatrix(screenOriginX,screenOriginY, worldUpVectorX,worldUpVectorY)
{
var worldUp = {x: worldUpVectorX, y: worldUpVectorY};
var len = Math.hypot(worldUp.x, worldUp.y);
if (len != 0)
len = 1.0 / len;
worldUp.x *= len;
worldUp.y *= len;
var worldRight = {x: worldUp.y, y: -worldUp.x};
var rotMat = svg.createSVGMatrix();
rotMat.a = worldRight.x;
rotMat.b = worldRight.y;
rotMat.c = worldUp.x;
rotMat.d = worldUp.y;
var transMat = svg.createSVGMatrix();
transMat = transMat.translate(screenOriginX, screenOriginY);
var result = rotMat.multiply(transMat);
return result;
}
function evaluateViewOrientationMatrix(screenOriginX,screenOriginY, worldUpVectorX,worldUpVectorY)
{
var worldUp = new vec2d(worldUpVectorX, worldUpVectorY);
worldUp.normalize();
var worldRight = worldUp.clone().perp();
var rotMat = new mat3();
rotMat.elems[0] = worldRight.x; rotMat.elems[1] = worldRight.y;
rotMat.elems[3] = worldUp.x; rotMat.elems[4] = worldUp.y;
var transMat = new mat3();
transMat.translate(screenOriginX,screenOriginY);
var result = rotMat.clone();
result.multiply(transMat);
return result;
}
/*
0 1 2
3 4 5
6 7 8
translation
-----------
1 0 0
0 1 0
tX tY 1
scaling
---------
sX 0 0
0 sY 0
0 0 1
rotation
--------
cosX -sinX 0
sinX cosX 0
0 0 1
*/
// Exercise 6-2:
// Derive the window to viewport transformation equations 6-3 by first scaling the window to
// the size of the viewport and then translating the scaled window to the viewport position
function computeWindowToViewPortMatrix(windowWidth,windowHeight,viewPortWidth,viewPortHeight)
{
var result = new mat3();
result.scaleEach(viewPortWidth/windowWidth,viewPortHeight/windowHeight);
return result;
}
// returns an SVGMatrix
function compWnd2ViewMat(windowWidth,windowHeight,viewPortWidth,viewPortHeight)
{
var result = svg.createSVGMatrix();
return result.scaleNonUniform(viewPortWidth/windowWidth,viewPortHeight/windowHeight);
}
function drawLine(x1,y1,x2,y2,ctx)
{
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
}
function drawCircle(x,y,radius,ctx)
{
ctx.beginPath();
ctx.arc(x, y, radius, 0, (Math.PI/180)*360, false);
ctx.stroke();
ctx.closePath();
}
canvas
{
background-color: black;
}
.container
{
display: inline-block;
background-color: #888;
border: solid 4px #555;
}
#screenMouse, #worldMouse, .control
{
display: inline-block;
width: calc(513px/2 - 2*8px);
margin-left: 8px;
}
<body>
<div class='container'>
<canvas id='output' width='513' height='329'></canvas><br>
<div id='screenMouse'></div><div id='worldMouse'></div>
<div>
<div class='control'>P2 ofs: <input id='slider1' type='range' min='0' max='500' value='301'><span id='ofset1Output'></span></div>
<div class='control'>P3 ofs: <input id='slider2' type='range' min='0' max='500' value='285'><span id='ofset2Output'></span></div>
</div>
</div>
</body>

How can one make an ellipse in react-leaflet?

I am trying to draw an ellipse on a map made using react-leaflet, which has built-in support for circles and rectangles.
To achieve this, I am using code to produce an ellipse in (non-react) leaflet from here, that I have adapted and pasted below:
import * as L from 'leaflet';
L.SVG.include ({
_updateEllipse: function (layer) {
var // c = layer._point,
rx = layer._radiusX,
ry = layer._radiusY,
phi = layer._tiltDeg,
endPoint = layer._endPointParams;
var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
'A' + rx + ',' + ry + ',' + phi + ',' +
endPoint.largeArc + ',' + endPoint.sweep + ',' +
endPoint.x1 + ',' + endPoint.y1 + ' z';
this._setPath(layer, d);
}
});
L.Canvas.include ({
_updateEllipse: function (layer) {
if (layer._empty()) { return; }
var p = layer._point,
ctx = this._ctx,
r = layer._radiusX,
s = (layer._radiusY || r) / r;
this._drawnLayers[layer._leaflet_id] = layer;
ctx.save();
ctx.translate(p.x, p.y);
if (layer._tilt !== 0) {
ctx.rotate( layer._tilt );
}
if (s !== 1) {
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.restore();
this._fillStroke(ctx, layer);
},
});
L.Ellipse = L.Path.extend({
options: {
fill: true,
startAngle: 0,
endAngle: 359.9
},
initialize: function (latlng, radii, tilt, options) {
L.setOptions(this, options);
this._latlng = L.latLng(latlng);
if (tilt) {
this._tiltDeg = tilt;
} else {
this._tiltDeg = 0;
}
if (radii) {
this._mRadiusX = radii[0];
this._mRadiusY = radii[1];
}
},
setRadius: function (radii) {
this._mRadiusX = radii[0];
this._mRadiusY = radii[1];
return this.redraw();
},
getRadius: function () {
return new L.point(this._mRadiusX, this._mRadiusY);
},
setTilt: function (tilt) {
this._tiltDeg = tilt;
return this.redraw();
},
getBounds: function () {
// TODO respect tilt (bounds are too big)
var lngRadius = this._getLngRadius(),
latRadius = this._getLatRadius(),
latlng = this._latlng;
return new L.LatLngBounds(
[latlng.lat - latRadius, latlng.lng - lngRadius],
[latlng.lat + latRadius, latlng.lng + lngRadius]);
},
// #method setLatLng(latLng: LatLng): this
// Sets the position of a circle marker to a new location.
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
this.redraw();
return this.fire('move', {latlng: this._latlng});
},
// #method getLatLng(): LatLng
// Returns the current geographical position of the circle marker
getLatLng: function () {
return this._latlng;
},
setStyle: L.Path.prototype.setStyle,
_project: function () {
var lngRadius = this._getLngRadius(),
latRadius = this._getLatRadius(),
latlng = this._latlng,
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);
this._point = this._map.latLngToLayerPoint(latlng);
this._radiusX = Math.max(this._point.x - pointLeft.x, 1);
this._radiusY = Math.max(pointBelow.y - this._point.y, 1);
this._tilt = Math.PI * this._tiltDeg / 180;
this._endPointParams = this._centerPointToEndPoint();
this._updateBounds();
},
_updateBounds: function () {
// http://math.stackexchange.com/questions/91132/how-to-get-the-limits-of-rotated-ellipse
var sin = Math.sin(this._tilt);
var cos = Math.cos(this._tilt);
var sinSquare = sin * sin;
var cosSquare = cos * cos;
var aSquare = this._radiusX * this._radiusX;
var bSquare = this._radiusY * this._radiusY;
var halfWidth = Math.sqrt(aSquare*cosSquare+bSquare*sinSquare);
var halfHeight = Math.sqrt(aSquare*sinSquare+bSquare*cosSquare);
var w = this._clickTolerance();
var p = [halfWidth + w, halfHeight + w];
this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
},
_update: function () {
if (this._map) {
this._updatePath();
}
},
_updatePath: function () {
this._renderer._updateEllipse(this);
},
_getLatRadius: function () {
return (this._mRadiusY / 40075017) * 360;
},
_getLngRadius: function () {
return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
},
_centerPointToEndPoint: function () {
// Convert between center point parameterization of an ellipse
// too SVG's end-point and sweep parameters. This is an
// adaptation of the perl code found here:
// http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
var c = this._point,
rx = this._radiusX,
ry = this._radiusY,
theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
theta1 = this.options.startAngle * (Math.PI / 180),
delta = this.options.endAngle,
phi = this._tiltDeg * (Math.PI / 180);
// Determine start and end-point coordinates
var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
Math.sin(-phi) * ry * Math.sin(theta1);
var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
Math.cos(phi) * ry * Math.sin(theta1);
var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
Math.sin(-phi) * ry * Math.sin(theta2);
var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
Math.cos(phi) * ry * Math.sin(theta2);
var largeArc = (delta > 180) ? 1 : 0;
var sweep = (delta > 0) ? 1 : 0;
return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
'sweep': sweep, 'x1': x1, 'y1': y1};
},
_empty: function () {
return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds);
},
_containsPoint : function (p) {
// http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
var sin = Math.sin(this._tilt);
var cos = Math.cos(this._tilt);
var dx = p.x - this._point.x;
var dy = p.y - this._point.y;
var sumA = cos * dx + sin * dy;
var sumB = sin * dx - cos * dy;
return sumA * sumA / (this._radiusX * this._radiusX) + sumB * sumB / (this._radiusY * this._radiusY) <= 1;
}
});
export const lellipse = function (latlng, radii, tilt, options) {
return new L.Ellipse(latlng, radii, tilt, options);
};
To create an ellipse to use with react-leaflet, I followed the example of Circle in react-leaflet to produce the following Ellipse component:
import PropTypes from 'prop-types'
import { lellipse as LeafletEllipse } from '../l.ellipse';
import Path from './Path'
import children from './propTypes/children'
import latlng from './propTypes/latlng'
import type { LatLng, MapLayerProps, PathOptions } from './types'
type LeafletElement = LeafletEllipse
type Props = {
center: LatLng,
mSemiMajorAxis: number,
mSemiMinorAxis: number,
degreeTiltFromWest: number,
} & MapLayerProps &
PathOptions &
Object
export default class Ellipse extends Path<LeafletElement, Props> {
static propTypes = {
center: latlng.isRequired,
mSemiMajorAxis: PropTypes.number.isRequired,
mSemiMinorAxis: PropTypes.number.isRequired,
degreeTiltFromWest: PropTypes.number.isRequired,
children: children,
}
createLeafletElement(props: Props): LeafletElement {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], this.getOptions(options))
}
updateLeafletElement(fromProps: Props, toProps: Props) {
if (toProps.center !== fromProps.center) {
this.leafletElement.setLatLng(toProps.center);
}
if (toProps.degreeTiltFromWest !== fromProps.degreeTiltFromWest) {
this.leafletElement.setTilt(toProps.degreeTiltFromWest);
}
if (toProps.mSemiMinorAxis !== fromProps.mSemiMinorAxis || toProps.mSemiMajorAxis !== fromProps.mSemiMajorAxis) {
this.leafletElement.setRadius([toProps.mSemiMajorAxis, toProps.mSemiMinorAxis]);
}
}
}
The problem with the code is that it does not render an ellipse and it does not throw any errors. Could someone suggest how to render an ellipse with react-leaflet? Thanks.
Your createLeafletElement function is missing the tilt parameter. It should be:
createLeafletElement(props) {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], degreeTiltFromWest, this.getOptions(options))
}
See below for the complete file (in ES6 rather than in typescript, as I find it clearer).
import React, { PropTypes } from 'react';
import { lellipse as LeafletEllipse } from './l.ellipse';
import { Path, withLeaflet } from 'react-leaflet';
class Ellipse extends Path {
static propTypes = {
center: PropTypes.arrayOf(PropTypes.number).isRequired,
mSemiMajorAxis: PropTypes.number.isRequired,
mSemiMinorAxis: PropTypes.number.isRequired,
degreeTiltFromWest: PropTypes.number.isRequired
}
createLeafletElement(props) {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], degreeTiltFromWest, this.getOptions(options))
}
updateLeafletElement(fromProps, toProps) {
if (toProps.center !== fromProps.center) {
this.leafletElement.setLatLng(toProps.center);
}
if (toProps.degreeTiltFromWest !== fromProps.degreeTiltFromWest) {
this.leafletElement.setTilt(toProps.degreeTiltFromWest);
}
if (toProps.mSemiMinorAxis !== fromProps.mSemiMinorAxis || toProps.mSemiMajorAxis !== fromProps.mSemiMajorAxis) {
this.leafletElement.setRadius([toProps.mSemiMajorAxis, toProps.mSemiMinorAxis]);
}
}
}
export default class withLeaflet(Ellipse);

Visualization of recursive Fibonacci calculation

Is there any program/website which can visualize a graph of a recursive Fibonacci calculation.
I want to show how many recursion steps will be needed.
I found this to be a neat little challenge, let me share my implementation:
var canvas = document.getElementById("canvas");
var width = canvas.width;
var height = canvas.height;
var ctx = canvas.getContext("2d");
var FN = 8;
var FONT_SIZE = 11; // in points
var SHOW_BOXES = false;
var SHOW_DISCS = true;
var SHOW_FIB_N = true;
function Tree(fn) {
var pdata = {};
pdata.lhs = null;
pdata.rhs = null;
pdata.fn = fn;
this.getLeft = function() { return pdata.lhs; };
this.setLeft = function(node) { pdata.lhs = node; };
this.getRight = function() { return pdata.rhs; };
this.setRight = function(node) { pdata.rhs = node; };
this.getFn = function() { return pdata.fn; };
}
function fib(n) {
if(n == 0)
return new Tree(0);
if(n == 1)
return new Tree(1);
else {
var lhs = fib(n-1);
var rhs = fib(n-2);
var root = new Tree(lhs.getFn() + rhs.getFn());
root.setLeft(lhs);
root.setRight(rhs);
return root;
}
}
var root = fib(FN);
function Box(x0, y0, x1, y1) {
if(arguments.length < 4) {
x0 = 1;
y0 = 1;
x1 = -1;
y1 = -1;
}
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
this.width = function() { return this.x1 - this.x0; };
this.height = function() { return this.y1 - this.y0; };
this.offset = function(x, y) {
this.x0 += x;
this.y0 += y;
this.x1 += x;
this.y1 += y;
};
this.extend = function(x, y) {
if(this.x1 < this.x0 || this.y1 < this.y0) {
this.x0 = x;
this.x1 = x;
this.y0 = y;
this.y1 = y;
} else {
this.x0 = this.x0 < x ? this.x0 : x;
this.y0 = this.y0 < y ? this.y0 : y;
this.x1 = this.x1 > x ? this.x1 : x;
this.y1 = this.y1 > y ? this.y1 : y;
}
}
};
(function () {
// assume spheres of radius 0.5
function setBounds(node, offX, offY) {
var bbox = new Box(offX, offY, offX + 1, offY + 1);
if(node.getLeft() != null || node.getRight() != null) {
var lhs = node.getLeft(), rhs = node.getRight();
if(lhs != null) {
setBounds(lhs, offX + 0, offY + 1.1);
bbox.extend(lhs.bbox.x0, lhs.bbox.y0);
bbox.extend(lhs.bbox.x1, lhs.bbox.y1);
}
if(rhs != null) {
setBounds(rhs, offX + (lhs != null ? lhs.bbox.width() : 0), offY + 1.1);
bbox.extend(rhs.bbox.x0, rhs.bbox.y0);
bbox.extend(rhs.bbox.x1, rhs.bbox.y1);
}
}
node.bbox = bbox;
}
setBounds(root, 0, 0);
})();
var transf = (function() {
var b = 2;
var sx = (width - 2 * b) / root.bbox.width();
var sy = (height - 2 * b) / root.bbox.height();
return {
ox: b / sx - root.bbox.x0,
oy: b / sx - root.bbox.y0,
sx: sx,
sy: sy,
};
})();
transf.smin = Math.min(transf.sx, transf.sy);
ctx.clearRect(0, 0, width, height);
(function(g) {
g.font = FONT_SIZE + "pt Arial";
g.textAlign = "center";
g.strokeStyle = "#000000";
function draw(node, pX, pY) {
if(node == null) return;
var cX = (node.bbox.x0 + node.bbox.x1) / 2;
var cY = (node.bbox.y0 + 0.5);
var radius = 0.475;
cX = transf.sx * (cX + transf.ox);
cY = transf.sy * (cY + transf.oy);
radius *= transf.smin;
draw(node.getLeft(), cX, cY);
draw(node.getRight(), cX, cY);
if(SHOW_BOXES) {
g.fillStyle = "#ff0000";
g.beginPath();
g.moveTo(transf.sx * (node.bbox.x0 + transf.ox), transf.sy * (node.bbox.y0 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x1 + transf.ox), transf.sy * (node.bbox.y0 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x1 + transf.ox), transf.sy * (node.bbox.y1 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x0 + transf.ox), transf.sy * (node.bbox.y1 + transf.oy));
g.closePath();
g.stroke();
}
if(SHOW_DISCS) {
if(arguments.length >= 3) {
g.beginPath();
g.moveTo(pX, pY);
g.lineTo(cX, cY);
g.stroke();
}
g.fillStyle = "#ff0000";
g.beginPath();
g.arc(cX, cY, radius, 0, 2 * Math.PI);
g.fill();
g.stroke();
}
if(SHOW_FIB_N) {
g.fillStyle = "#0000ff";
g.fillText(node.getFn(), cX, cY + FONT_SIZE / 2);
}
}
draw(root);
})(ctx);
<canvas id="canvas" width="800" height="480" />