Summary
I'm trying to read a .SCT file which is a custom file type that is created by a program named VRC. I use this file to plot lat long coordinates within my source area. The issue I am having is that most of the lat long coordinates are not being mapped correctly. It appears that most coordinates are mapping to some arbitrary point.
Background
Here is a sample of the code I'm currently using to convert and plot them into the SVG.js canvas.
CODE [index.js (at least the active part)]:
var coor = [];
let fixed = [];
var dms2dd = function(object) {
var deg = parseFloat(object.degrees),
min = parseFloat(object.minutes),
sec = parseFloat(object.seconds),
dir = object.dir == "N" || object.dir == "E" ? 1 : -1;
return dir*(deg+(min/60.0)+(sec/3600.0));
};
function llToXY(arr) {
mapWidth = 1000;
mapHeight = 1000;
// get x value
x = (arr[1]+180)*(mapWidth/360)
// convert from degrees to radians
latRad = arr[0]*Math.PI/180;
// get y value
mercN = Math.log(Math.tan((Math.PI/4)+(latRad/2)));
y = (mapHeight/2)-(mapWidth*mercN/(2*Math.PI));
return [x, y];
}
lineReader.eachLine('test.txt', function(line, last) {
let data = line.split(" ");
let coor_length = coor.length;
if (line[0] != " ") {
coor.push({
type: data[0],
coordinates: []
});
// Convert to DD
/*let direction = data[1].substr(0, 1);
let dms = data[1].split(".")
dms2dd({
})*/
data.splice(0, 1);
coor[coor.length - 1]["coordinates"].push(data.join(" "));
} else {
coor[coor_length - 1]["coordinates"].push(line.trim())
}
if (last) {
coor.forEach((data, index) => {
for (coordinate_pair in data["coordinates"]) {
let pair = data["coordinates"][coordinate_pair];
pair = pair.split(" ");
let x_data = pair[0].split("."),
y_data = pair[1].split(".");
let x = dms2dd({
degrees: x_data[0].substring(1),
minutes: parseFloat(x_data[1]),
seconds: parseFloat(`${x_data[2]}.${x_data[3]}`),
dir: x_data[0].substr(0,1)
});
let y = dms2dd({
degrees: y_data[0].substring(1),
minutes: parseFloat(y_data[1]),
seconds: parseFloat(`${y_data[2]}.${y_data[3]}`),
dir: y_data[0].substr(0,1)
});
console.log([x, y]);
coor[index]["coordinates"][coordinate_pair] = llToXY([x, y]);
}
})
return false;
}
});
Drawing Code
let draw = SVG("drawing").size(1000, 1000).panZoom();
let cp = <%- JSON.stringify(cp) %>;
//var line = draw.plot([32.737396,117.204284], [32.736862,117.204468], [32.737396,117.204284], [32.736862,117.204468]).stroke({ width: 1 })
// var line = draw.polyline().fill("none").stroke({width: 0.00005});
// line.transform({
// scale: 50000
// }).transform({rotation: 104.5});
cp.forEach((data)=> {
//draw.polyline(data.coordinates.join(" "))
//llToXY(data.coordinates);
draw.polyline(data.coordinates.join(" ")).fill("none").stroke({width: 0.00005}).transform({scale: 50000}).transform({rotation: -15.80})
});
This code is basically reading from a text file line-by-line and inserting the data into a variable named coor. Once the code reaches the last line, it will convert all coordinates i.e. coor values to decimal degrees.
Unfortunately, the library is not compatible with JSFiddle so I couldn't make a test scenario. I've attached all the necessary files, so you can run locally.
My main concern is that all coordinates are mapping to some arbitrary point.
Sources
This is what the image should look like
What it currently looks like:
VRC Sector File Documentation: http://www1.metacraft.com/VRC/docs/doc.php?page=appendix_g
StackOverflow Question Referenced: Convert latitude/longitude point to a pixels (x,y) on mercator projection
Library Used
svg.js: https://svgjs.dev/
svg.panzoom.js: https://github.com/svgdotjs/svg.panzoom.js
That is actually an issue with bad documentation for the SVG.js library. If you define a transformation as
element.transform({ scale: 30000 })
the result is not a transform attribute with the value scale(30000), which would mean an origin for the scaling at point (0, 0), but a transform matrix that is equivalent to a scaling around the center of the element bounding box.
In your code, each partial shape is drawn as a separate polyline, and is separately scaled around its individual center. The element, before the scaling, is extremely small, and all elements are as closely grouped together as to be virtually at one point. If they are scaled , the result looks like all elements have that point as one common center at their new size.
The most obvious solution is to scale the elements not around their individual center, but around one constant value:
const cx = ..., cy = ...
element.transform({ scale: 30000, cx, cy })
What that value is is not immediately clear. It would be a point that is in the center of the common bounding box of all polylines. How do you get at that? Let the library do the work for you.
If you add all polylines as childs of a <g> element, you can scale that group, and if you leave out values for the center, they will be computed for you:
let draw = SVG("drawing").size(1000, 1000).panZoom();
let g = draw.group();
let cp = <%- JSON.stringify(cp) %>;
cp.forEach((data) => {
group.polyline(data.coordinates.join(" "))
.fill("none")
.stroke({width: 0.00005});
});
group.transform({scale: 50000}).transform({rotation: -15.80});
The above solution is good if you want to get your resulting map at a defined size. If you want to find a scale value that actually lets the content fill your canvas from side to side, it is just as simple: you can get the bounding box of of the group, and set them as a viewbox on the <svg> element. The browser will then take care to scale that box to fit the canvas.
let draw = SVG("drawing").size(1000, 1000).panZoom();
let g = draw.group();
let cp = <%- JSON.stringify(cp) %>;
cp.forEach((data) => {
group.polyline(data.coordinates.join(" "))
.fill("none")
.stroke({width: 0.00005});
});
const { x, y, w, h } = group.bbox();
draw.viewbox(x, y w, h);
Related
I have an imported SVG in my paper.js project.
What i'm trying to do is to rotate each of the svg's segment points around a set radius, animating it with the onFrame() method.
I know how to position each segments point's x and y position but i cant really figure out how to position it in a 'rotation'.
What i have now :
var words = project.importSVG(document.querySelector("svg"));
words.visible = true; // Turn off the effect of display:none;;
words.position = view.center;
var letterR = words.children.letter_r;
var letterR_outside = letterR.children.letter_r_outside;
letterR_outside.selected = true;
var rotate_point = view.center;
function onFrame(event) {
var _delta = Math.sin(event.time);
// console.log(Math.round(50 * Math.cos(theta[0])) * _delta);
// letterR_outside.segments[0].point.x = Math.round(_radius * Math.cos(theta[0]));
// letterR_outside.segments[0].point.y = Math.round(_radius * Math.sin(theta[0]));
for (var i = 0; i < letterR_outside.segments.length; i++) {
var segment = letterR_outside.segments[i];
// segment.point.x += _delta;
segment.point.rotate(3, rotate_point);
}
}
Example of what i'm trying to achieve by rotating each points around a radius. [i made the black circles visible to show the rotation circle of each points]
I'm not exactly sure how to approach this. Any help is greatly appreciated
Calling the point.rotate() does nothing because it returns a clone of the point and doesn't modify it directly.
See the documentation:
Rotates the point by the given angle around an optional center point. The object itself is not modified. ...
So you might want to reasign the value:
segment.point = segment.point.rotate(3, rotate_point);
I want to be able to calculate the surface area of a 2D polygon of any shape, given a set of 3D vertices. For example, what is the surface area of this figure?
var polygon = new Polygon([new Point(0,0,0), new Point(5,8,2), new Point(11,15,7)])
polygon.areaIfPolygonIs3D()
--> some predictable result, no matter how many vertices the polygon has...
Keep in mind that polygons only have one surface. They are flat but could be triangle shaped or trapezoid shaped or randomly shaped, and could be floating at a 3D angle... imagine them as pieces of paper turned any which way in 3D space.
What I've tried to do so far is rotate the thing flat, and then use a basic formula for calculating the area of a 2D irregular polygon which is currently working in my code (formula: http://www.wikihow.com/Calculate-the-Area-of-a-Polygon). I had such a hard figuring out how to rotate all the vertices so the polygon lays flat (all "z" values are 0) that I abandoned that path, though I'm open to trying it if someone can get there. (Perhaps there is a bug in Point.rotateBy().)
I can work with Points, and Edges (created with point.to(point)), and Edges have 'theta' (edge.theta()) and 'phi' (edge.phi()).
In any case, if someone can fill in what goes here and help me after a full days effort of trying to relearn all the geometry I forgot from high school, that would be much appreciated!
var locatorRho = function(x,y,z) {
return Math.sqrt(x*x + y*y + z*z);
}
var locatorTheta = function(x,y) {
return Math.atan2(y,x);
};
var locatorPhi = function(x,y,z) {
return z == 0 ? Math.PI_2 : Math.acos(z/locatorRho(x, y, z));
}
// rotates a point according to another point ('locator'), and their 2D angle ('theta') and 3D angle ('phi')
Point.prototype.rotateBy = function(locator, theta, phi) {
phi = (phi == undefined ? 0 : phi);
var relativeX = this.x() - locator.x();
var relativeY = this.y() - locator.y();
var relativeZ = this.z() - locator.z();
var distance = locatorRho(relativeX, relativeY, relativeZ);
var newTheta = locatorTheta(relativeX, relativeY) + theta;
var newPhi = locatorPhi(relativeX, relativeY, relativeZ) + phi;
this._x = locatorX(distance, newTheta, newPhi) + locator.x();
this._y = locatorY(distance, newTheta, newPhi) + locator.y();
this._z = locatorZ(distance, newPhi) + locator.z();
}
Polygon.prototype.signedArea = function() {
var vertices = this.vertices();
var area = 0;
for(var i=0, j=1, length=vertices.length; i<length; ++i, j=(i+1)%length) {
area += vertices[i].x()*vertices[j].y() - vertices[j].x()*vertices[i].y();
}
return 0.5*area
}
Polygon.prototype.areaIfPolygonIs2D = function() {
return Math.abs(rotatedFlatCopy.signedArea())
}
Polygon.prototype.areaIfPolygonIs3D = function() {
... help here I am so stuck ...
}
var vertices = [some number of Points, e.g., new Point(x,y,z)]
var polygon = new Polygon(vertices)
var polygon.areaIfPolygonIs3D()
--> result
If your polygon plane is not parallel to Z axis, you can calculate area projection with known approach using X and Y coordinates only, then divide result by cosine of angle between Z axis and normal N to that plane
Area = Sum[x1*y2-x2*y1 +...] ////shoelace formula
True_Area = Area / Cos(Angle between N and Z axis)) =
Area / DotProduct((N.x,N.y,N.z), (0,0,1)) =
Area / N.z
//// if N is normalized (unit)
Use the shoelace formula three times, on the 2D vertices (X, Y), (Y, Z) and (Z, X). The desired area is given by √Axy²+Ayz²+Azx² (provided the polygon is flat).
Hey guys I have been working on a little project of mine utilizing the awesome library three.js
Now I have been working with the example of https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/Collision-Detection.html to handle collision detection, more so on when an object over laps another object utilizing voxel's.
To reference my problem I am using the http://threejs.org/examples/#canvas_interactive_voxelpainter example.
Anyways to continue, when I render a voxel onto the screen, anything above the cube will allow me to render another voxel anything within a certain radius below the volex will not let me render:
Here is shown above cube:
Now here is my neat little function I put together using the example provided by stemkoski:
checkOverlapObject: function(voxel) // THIS IS USED TO SEE IF WE ARE OVER LAPPING ANY OBJECTS
{
var originPoint = voxel.position.clone();
var collidableObjs = this.rooms;
for (var vertexIndex = 0; vertexIndex < voxel.geometry.vertices.length; vertexIndex++)
{
var localVertex = voxel.geometry.vertices[vertexIndex].clone();
console.log(localVertex);
var globalVertex = localVertex.applyMatrix4( voxel.matrix );
console.log(globalVertex);
var directionVector = globalVertex.sub( voxel.position );
console.log(directionVector);
console.log(originPoint);
console.log(directionVector.clone().normalize());
if(collidableObjs.length > 0)
{
var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableObjs );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
{
console.log(collisionResults);
console.log(collisionResults[0].distance);
console.log( directionVector.length() );
return false
}
}
}
return true;
},
Now what happens here is, before actually adding a rendered volex the user gets a preview of if they have permission to add the volex So we pass a volex made by:
var voxel = new THREE.Mesh( this.room.cubeGeometry, this.room.cubeTmpHoverMaterial );
voxel.geometry.computeBoundingBox();
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
voxel.material.color.setHex(this.colorTmpHover);
into our checkOverlapObject function to see if the object is overlapping a object that has been rendered onto the screen/grid
Now following my little neat function I made, I have placed console.log to output parameters and here is some of that output:
T…E.Vector3 {x: 25, y: 25, z: 25} <!-- our localVertex
T…E.Vector3 {x: 25, y: 25, z: 25} <!-- our globalVertex
T…E.Vector3 {x: 0, y: 0, z: -350} <!-- our directionVector
T…E.Vector3 {x: 25, y: 25, z: 375} <!-- our originPoint
T…E.Vector3 {x: 0, y: 0, z: -1} <!-- our directionVector.clone().normalize()
[Object, Object] <!-- our collisionResults
225 <!-- our collisionResults[0].distance
350 <!-- our directionVector.length()
This data is based off of the first picture.
Please understand that I have other volex that take up 2 blocks on the grid or more. So the reason for this is, I have a center location of the position but I need to take into account the rest of the object if it takes up 2 blocks on the grid to check if that overlaps the already rendered volex I don't care if they touch each other.
Any suggestions or thoughts as to what might be the problem?
As asked above I finally found a solution. I created a custom function for object collision/overlap.
Firstly, here is whats wrong with the above collision code as shown above, it process's the raycaster starting with the direction of our object - shooting a line straight across from its orgin to see if anything comes into contact with that ray. Now that is great and all but not very sufficient in telling when we have a overlap/collision if any part of the object is touching another object
As shwon with our rays:
So here is my solution.
When ever we have an object on our plane, we know everything about it, our coordinates of x,y,z - I am not dealing with y in this example so ignore that.
What we care about is our objects width = x and our objects depth/length = z. So in essence when we are checking to see if an object is overlapping another object we simply need to process through each min x/z and max x/z of our to be added object and our already rendered obj
How we do that is, loop through our object to be added starting min z position with a for loop. This starting min z position contains our position z + min z of object. This gives us the exact z position on our plane.
We than move on to our width or our object to be added starting min x position with a for loop. Don't forget our x gives us the exact position on our plane
Now the reason for this is simple. We want to start at position 0,0 of our object to be added. Increment by one up our length/z - increment all the way down our width/x and while doing this process each object on our plane that is already rendered and check to see if any exact point matches ours.
Here is the actual method:
checkOverlapObject: function(voxel) // THIS IS USED TO SEE IF WE ARE OVER LAPPING ANY OBJECTS
{
var collidableObjs = this.rooms;
//lets get our voxel min and max vars
var voxelMX = voxel.geometry.boundingBox.max.x;
var voxelMZ = voxel.geometry.boundingBox.max.z;
var voxelmX = voxel.geometry.boundingBox.min.x;
var voxelmZ = voxel.geometry.boundingBox.min.z;
// we need to get our voxel position ad to do some math to see if voxel min and max do not go beyound our boundries for our plane
var voxelPX = voxel.position.x;
var voxelPZ = voxel.position.z;
var totalPositionVoxelminZ = (voxelPZ + voxelmZ);
var totalPositionVoxelminX = (voxelPX + voxelmX);
var totalPositionVoxelMAXZ = (voxelPZ + voxelMZ);
var totalPositionVoxelMAXX = (voxelPX + voxelMX);
// start loop for object to add Z cordinate
for(var length = totalPositionVoxelminZ; length < totalPositionVoxelMAXZ; length++)
{
// start loop for object to add X cordinate
for(var width = totalPositionVoxelminX; width < totalPositionVoxelMAXX; width++)
{
for(var i = 0; i < collidableObjs.length;i++)
{
//lets get our voxel min and max vars
var thisvoxelMX = this.rooms[i].geometry.boundingBox.max.x;
var thisvoxelMZ = this.rooms[i].geometry.boundingBox.max.z;
var thisvoxelmX = this.rooms[i].geometry.boundingBox.min.x;
var thisvoxelmZ = this.rooms[i].geometry.boundingBox.min.z;
// we need to get our voxel position ad to do some math to see if voxel min and max do not go beyound our boundries for our plane
var thisvoxelPX = this.rooms[i].position.x;
var thisvoxelPZ = this.rooms[i].position.z;
var thistotalPositionVoxelminZ = (thisvoxelPZ + thisvoxelmZ);
var thistotalPositionVoxelminX = (thisvoxelPX + thisvoxelmX);
var thistotalPositionVoxelMAXZ = (thisvoxelPZ + thisvoxelMZ);
var thistotalPositionVoxelMAXX = (thisvoxelPX + thisvoxelMX);
for(var insideZ = thistotalPositionVoxelminZ; insideZ < thistotalPositionVoxelMAXZ; insideZ++)
{
for(var insideX = thistotalPositionVoxelminX; insideX < thistotalPositionVoxelMAXX; insideX++)
{
if(insideZ == length && insideX == width)
{
return false;
}
}
}
}
}
}
return true;
}
Here is our result:
This really does provide an exact answer on if your object is touching something it should not be touching because of the point by point it processes.
I hope this helps! Please feel free to use this at anytime.
Also note this really could effect overhead/memory usage if you are not careful on how you use this. I am working on a way to better optimize this in the case of having hundred of objects to process. So if you have any suggestions on modifying or adding to my existing code to accomplish this better and offer better performance in case of hundreds of objects passed, feel free provide details!
I'm using D3 to create an organization chart. I've got the data loading fine and have figured out how to make the canvas move by dragging the mouse as well a zoom with the mouse wheel.
My problem is that the org chart is rather large so when the document first loads the root node is out of the browser's view area and the zoom level is set fairly high.
I need to figure out how to set the viewable area of the canvas around the first node and set the initial zoom level to 100%.
I was able to create a solution thanks to #Lars Kotthoff.
I retrieved the root node's x value from it's translate attribute (i.e. translate(x,y)) and then took the browser's width / 2 - the x value. I applied this value to the parent group's translate attribute which centers the document around the root node.
var windowWidth = $(window).width();
var node0 = d3.select("#node-0");
var translate = parseTranslate(node0.attr("transform"));
var translateX = translate.x - (windowWidth / 2);
var svgGroup = d3.select("#svg_g");
svgGroup.attr("transform", "translate(-" + translateX + ",22) scale(1)"); // with 20 y padding
NOTE: Because I'm new to SVG and D3 I am still not sure how to get just the "x" value of a node's translate attribute so I created a function that parses the translate attribute with regex. I'm sure there is a better way of getting this value so if anyone wants to update my answer or add a comment for future readers that would increase the value of this question.
The function I created is:
function parseTranslate(str) {
var translate = {
x: 0,
y: 0,
scale: 0
}
var pattern = /\((.+?)\)/g;
var matches = [];
while (match = pattern.exec(str)) {
matches.push(match[1]);
}
if (matches.length) {
if (matches.length == 1) {
if (matches[0].indexOf(",") > -1) {
var p = matches[0].split(',');
translate.x = p[0];
translate.y = p[1];
} else {
translate.scale = matches[0];
}
} else if (matches.length == 2) {
var p = matches[0].split(',');
translate.x = p[0];
translate.y = p[1];
translate.scale = matches[1];
}
}
return translate;
}
I'm also using jQuery in my project to get the width of the browser (ex: $(window).width();)
Over the last two days I've effectively figured out how NOT to rotate Raphael Elements.
Basically I am trying to implement a multiple pivot points on element to rotate it by mouse.
When a user enters rotation mode 5 pivots are created. One for each corner of the bounding box and one in the center of the box.
When the mouse is down and moving it is simple enough to rotate around the pivot using Raphael elements.rotate(degrees, x, y) and calculating the degrees based on the mouse positions and atan2 to the pivot point.
The problem arises after I've rotated the element, bbox, and the other pivots. There x,y position in the same only there viewport is different.
In an SVG enabled browser I can create new pivot points based on matrixTransformation and getCTM. However after creating the first set of new pivots, every rotation after the pivots get further away from the transformed bbox due to rounding errors.
The above is not even an option in IE since in is VML based and cannot account for transformation.
Is the only effective way to implement
element rotation is by using rotate
absolute or rotating around the center
of the bounding box?
Is it possible at all the create multi
pivot points for an object and update
them after mouseup to remain in the
corners and center of the transformed
bbox?
UPDATE:
I've attempted to use jQuery offset to find the pivot after it's been rotated, and to use that offset location as the pivot point.
Demo site ...
http://weather.speedfetishperformance.com/dev/raphael/rotation.html
The best cross-browser way I can think of to do what you want is to implement the rotation yourself rather than let SVG do it. Rotating x,y coordinates is fairly simple and I've been using this (tcl) code whenever I need to do 2D rotation: Canvas Rotation.
The upside to this is you have maximum control of the rotation since you're doing it manually. This solves the problems you're having trying to guess the final coordinates after rotation. Also, this should be cross browser compatible.
The downside is you have to use paths. So no rects (though it should be easy to convert them to paths) or ellipses (a little bit harder to convert to path but doable). Also, since you're doing it manually, it should be slower than letting SVG do it for you.
Here's a partial implementation of that Tcl code in javascript:
first we need a regexp to tokenize SVG paths:
var svg_path_regexp = (function(){
var number = '-?[0-9.]+';
var comma = '\s*[, \t]\s*';
var space = '\s+';
var xy = number + comma + number;
var standard_paths = '[mlcsqt]';
var horiz_vert = '[hv]\s*' + number;
var arc = 'a\s*' + xy + space + number + space + xy + space + xy;
var OR = '\s*|';
return new RegExp(
standard_paths +OR+
xy +OR+
horiz_vert +OR+
arc,
'ig'
);
})();
now we can implement the rotate function:
function rotate_SVG_path (path, Ox, Oy, angle) {
angle = angle * Math.atan(1) * 4 / 180.0; // degrees to radians
var tokens = path.match(svg_path_regexp);
for (var i=0; i<tokens.length; i++) {
var token = tokens[i].replace(/^\s+|\s+$/g,''); // trim string
if (token.match(/\d/)) { // assume it's a coordinate
var xy = token.split(/[, \t]+/);
var x = parseFloat(xy[0]);
var y = parseFloat(xy[1]);
x = x - Ox; // Shift to origin
y = y - Oy;
var xx = x * Math.cos(angle) - y * Math.sin(angle); // Rotate
var yy = x * Math.sin(angle) + y * Math.cos(angle);
x = xx + Ox; // Shift back
y = yy + Oy;
token = x + ',' + y;
}
else if (token.match(/^[hv]/)) {
// handle horizontal/vertical line here
}
else if (token.match(/^a/)) {
// handle arcs here
}
tokens[i] = token;
}
return tokens.join('');
}
The above rotate function implements everything except horizontal/vertical lines (you need to keep track of previous xy value) and arcs. Neither should be too hard to implement.