blob: 1c8607048a91f387f08bfad8e936420bd816229d [file] [log] [blame]
/*
ECMAScript helper functions
Copyright (C) <2004> <Andreas Neumann>
Version 1.1, 2004-11-18
neumann@karto.baug.ethz.ch
http://www.carto.net/
http://www.carto.net/neumann/
Credits: numerous people on svgdevelopers@yahoogroups.com
This ECMA script library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library (http://www.carto.net/papers/svg/resources/lesser_gpl.txt); if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
----
original document site: http://www.carto.net/papers/svg/resources/helper_functions.js
Please contact the author in case you want to use code or ideas commercially.
If you use this code, please include this copyright header, the included full
LGPL 2.1 text and read the terms provided in the LGPL 2.1 license
(http://www.gnu.org/copyleft/lesser.txt)
-------------------------------
Please report bugs and send improvements to neumann@karto.baug.ethz.ch
If you use these scripts, please link to the original (http://www.carto.net/papers/svg/navigationTools/)
somewhere in the source-code-comment or the "about" of your project and give credits, thanks!
*/
//global variables necessary to create elements in these namespaces, do not delete them!!!!
var svgNS = "http://www.w3.org/2000/svg";
var xlinkNS = "http://www.w3.org/1999/xlink";
var cartoNS = "http://www.carto.net/attrib";
var attribNS = "http://www.carto.net/attrib";
var batikNS = "http://xml.apache.org/batik/ext";
/* ----------------------- helper functions to calculate stuff ---------------- */
/* ---------------------------------------------------------------------------- */
function toPolarDir(xdiff,ydiff) { // Subroutine for calculating polar Coordinates
direction = (Math.atan2(ydiff,xdiff));
//result is angle in radian
return(direction);
}
function toPolarDist(xdiff,ydiff) { // Subroutine for calculating polar Coordinates
distance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
return(distance);
}
function toRectX(direction,distance) { // Subroutine for calculating cartesic coordinates
x = distance * Math.cos(direction);
y = distance * Math.sin(direction);
return(x);
}
function toRectY(direction,distance) { // Subroutine for calculating cartesic coordinates
x = distance * Math.cos(direction);
y = distance * Math.sin(direction);
return(y);
}
//Converts degrees to radians.
function DegToRad(deg) {
return (deg / 180.0 * Math.PI);
}
//Converts radians to degrees.
function RadToDeg(rad) {
return (rad / Math.PI * 180.0);
}
//converts decimal degrees to degree/minutes/seconds
function dd2dms(dd) {
var minutes = (Math.abs(dd) - Math.floor(Math.abs(dd))) * 60;
var seconds = (minutes - Math.floor(minutes)) * 60;
var minutes = Math.floor(minutes);
var degrees = Math.floor(dd);
return {deg:degrees,min:minutes,sec:seconds};
}
//converts degree/minutes/seconds to decimal degrees
function dms2dd(deg,min,sec) {
return deg + (min / 60) + (sec / 3600);
}
//log functions that do not exist in Math object
function log(x,b) {
if(b==null) b=Math.E;
return Math.log(x)/Math.log(b);
}
//gets 4 z-values (4 corners), a position, delta x and delty and a cellsize as input and returns interpolated z-value
function intBilinear(za,zb,zc,zd,xpos,ypos,ax,ay,cellsize) { //bilinear interpolation function
e = (xpos - ax) / cellsize;
f = (ypos - ay) / cellsize;
//calculation of weights
wa = (1 - e) * (1 - f);
wb = e * (1 - f);
wc = e * f;
wd = f * (1 - e);
height_interpol = wa * zc + wb * zd + wc * za + wd * zb;
return (height_interpol);
}
//test if point is left of or right of, result is 1 (leftof) or 0 (rightof)
function leftOfTest(pointx,pointy,linex1,liney1,linex2,liney2) {
result = (liney1 - pointy) * (linex2 - linex1) - (linex1 - pointx) * (liney2 - liney1);
if (result < 0) {
leftof = 1; //case left of
}
else {
leftof = 0; //case left of
}
return(leftof);
}
//input is point coordinate, and 2 line coordinates
function distFromLine(xpoint,ypoint,linex1,liney1,linex2,liney2) {
dx = linex2 - linex1;
dy = liney2 - liney1;
distance = (dy * (xpoint - linex1) - dx * (ypoint - liney1)) / Math.sqrt(Math.pow(dx,2) + Math.pow(dy,2));
return(distance);
}
//converts radian value to degrees
function radian2deg(radian) {
deg = radian / Math.PI * 180;
return(deg);
}
//input is two vectors (a1,a2 is vector a, b1,b2 is vector b), output is angle in radian
//Formula: Acos from Scalaproduct of the two vectors divided by ( norm (deutsch Betrag) vector 1 by norm vector 2
//see http://www.mathe-online.at/mathint/vect2/i.html#Winkel
function angleBetwTwoLines(a1,a2,b1,b2) {
angle = Math.acos((a1 * b1 + a2 * b2) / (Math.sqrt(Math.pow(a1,2) + Math.pow(a2,2)) * Math.sqrt(Math.pow(b1,2) + Math.pow(b2,2))));
return(angle);
}
//input is two vectors (a1,a2 is vector a, b1,b2 is vector b), output is new vector c2 returned as array
//Formula: Vektor a divided by Norm Vector a (Betrag) plus Vektor b divided by Norm Vector b (Betrag)
//see http://www.mathe-online.at/mathint/vect1/i.html#Winkelsymmetrale
function calcBisectorVector(a1,a2,b1,b2) {
betraga = Math.sqrt(Math.pow(a1,2) + Math.pow(a2,2));
betragb = Math.sqrt(Math.pow(b1,2) + Math.pow(b2,2));
c = new Array();
c[0] = a1 / betraga + b1 / betragb;
c[1] = a2 / betraga + b2 / betragb;
return(c);
}
//input is two vectors (a1,a2 is vector a, b1,b2 is vector b), output is angle in radian
//Formula: Vektor a divided by Norm Vector a (Betrag) plus Vektor b divided by Norm Vector b (Betrag)
//see http://www.mathe-online.at/mathint/vect1/i.html#Winkelsymmetrale
function calcBisectorAngle(a1,a2,b1,b2) {
betraga = Math.sqrt(Math.pow(a1,2) + Math.pow(a2,2));
betragb = Math.sqrt(Math.pow(b1,2) + Math.pow(b2,2));
c1 = a1 / betraga + b1 / betragb;
c2 = a2 / betraga + b2 / betragb;
angle = toPolarDir(c1,c2);
return(angle);
}
function intersect2lines(line1x1,line1y1,line1x2,line1y2,line2x1,line2y1,line2x2,line2y2) {
//formula see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/
var result = new Array();
var denominator = (line2y2 - line2y1)*(line1x2 - line1x1) - (line2x2 - line2x1)*(line1y2 - line1y1);
if (denominator == 0) {
alert("lines are parallel");
}
else {
ua = ((line2x2 - line2x1)*(line1y1 - line2y1) - (line2y2 - line2y1)*(line1x1 - line2x1)) / denominator;
ub = ((line1x2 - line1x1)*(line1y1 - line2y1) - (line1y2 - line1y1)*(line1x1 - line2x1)) / denominator;
}
result["x"] = line1x1 + ua * (line1x2 - line1x1);
result["y"] = line1y1 + ua * (line1y2 - line1y1);
return(result);
}
/* ----------------------- helper function to sort arrays ---------------- */
/* ----------------------------------------------------------------------- */
//my own sort function, uses only first part of string (population value)
function mySort(a,b) {
var myResulta = a.split("+");
var myResultb = b.split("+");
if (parseFloat(myResulta[0]) < parseFloat(myResultb[0])) {
return 1;
}
else {
return -1;
}
}
/* ----------------------- helper function format number strings -------------- */
/* ---------------------------------------------------------------------------- */
//formatting number strings
//this function add's "'" to a number every third digit
function formatNumberString(myString) {
//check if of type string, if number, convert it to string
if (typeof(myString) == "number") {
myTempString = myString.toString();
}
else {
myTempString = myString;
}
var myNewString="";
//if it contains a comma, it will be split
var splitResults = myTempString.split(".");
var myCounter= splitResults[0].length;
if (myCounter > 3) {
while(myCounter > 0) {
if (myCounter > 3) {
myNewString = "," + splitResults[0].substr(myCounter - 3,3) + myNewString;
}
else {
myNewString = splitResults[0].substr(0,myCounter) + myNewString;
}
myCounter -= 3;
}
}
else {
myNewString = splitResults[0];
}
//concatenate if it contains a comma
if (splitResults[1]) {
myNewString = myNewString + "." + splitResults[1];
}
return myNewString;
}
//function for status Bar
function statusChange(statusText) {
document.getElementById("statusText").firstChild.nodeValue = "Statusbar: " + statusText;
}
//scale an object
function scaleObject(evt,factor) {
//reference to the currently selected object
var element = evt.currentTarget;
var myX = element.getAttributeNS(null,"x");
var myY = element.getAttributeNS(null,"y");
var newtransform = "scale(" + factor + ") translate(" + (myX * 1 / factor - myX) + " " + (myY * 1 / factor - myY) +")";
element.setAttributeNS(null,'transform', newtransform);
}
//this code is copied from Kevin Lindsey
//http://www.kevlindev.com/tutorials/basics/transformations/toUserSpace/index.htm
function getTransformToRootElement(node) {
try {
//this part is for fully conformant players
var CTM = node.getTransformToElement(document.documentElement);
}
catch (ex) {
//this part is for ASV3 or other non-conformant players
// Initialize our CTM the node's Current Transformation Matrix
var CTM = node.getCTM();
// Work our way through the ancestor nodes stopping at the SVG Document
while ( ( node = node.parentNode ) != document ) {
// Multiply the new CTM to the one with what we have accumulated so far
CTM = node.getCTM().multiply(CTM);
}
}
return CTM;
}
//this is because ASV does not implement the method SVGLocatable.getTransformToElement()
function getTransformToElement(node,targetNode) {
try {
//this part is for fully conformant players
var CTM = node.getTransformToElement(targetNode);
}
catch (ex) {
//this part is for ASV3 or other non-conformant players
// Initialize our CTM the node's Current Transformation Matrix
var CTM = node.getCTM();
// Work our way through the ancestor nodes stopping at the SVG Document
while ( ( node = node.parentNode ) != targetNode ) {
// Multiply the new CTM to the one with what we have accumulated so far
CTM = node.getCTM().multiply(CTM);
}
}
return CTM;
}
//calculate HSV 2 RGB: HSV (h 0 to 360, sat and val are between 0 and 1), RGB between 0 and 255
function hsv2rgb(hue,sat,val) {
//alert("Hue:"+hue);
var rgbArr = new Array();
if ( sat == 0) {
rgbArr["red"] = Math.round(val * 255);
rgbArr["green"] = Math.round(val * 255);
rgbArr["blue"] = Math.round(val * 255);
}
else {
var h = hue / 60;
var i = Math.floor(h);
var f = h - i;
if (i % 2 == 0) {
f = 1 - f;
}
var m = val * (1 - sat);
var n = val * (1 - sat * f);
switch(i) {
case 0:
rgbArr["red"] = val;
rgbArr["green"] = n;
rgbArr["blue"] = m;
break;
case 1:
rgbArr["red"] = n;
rgbArr["green"] = val;
rgbArr["blue"] = m;
break;
case 2:
rgbArr["red"] = m;
rgbArr["green"] = val;
rgbArr["blue"] = n;
break;
case 3:
rgbArr["red"] = m;
rgbArr["green"] = n;
rgbArr["blue"] = val;
break;
case 4:
rgbArr["red"] = n;
rgbArr["green"] = m;
rgbArr["blue"] = val;
break;
case 5:
rgbArr["red"] = val;
rgbArr["green"] = m;
rgbArr["blue"] = n;
break;
case 6:
rgbArr["red"] = val;
rgbArr["green"] = n;
rgbArr["blue"] = m;
break;
}
rgbArr["red"] = Math.round(rgbArr["red"] * 255);
rgbArr["green"] = Math.round(rgbArr["green"] * 255);
rgbArr["blue"] = Math.round(rgbArr["blue"] * 255);
}
return rgbArr;
}
//calculate rgb to hsv values
function rgb2hsv (red,green,blue) {
//input between 0 and 255 --> normalize to 0 to 1
//result =
var hsvArr = new Array();
red = red / 255;
green = green / 255;
blue = blue / 255;
myMax = Math.max(red, Math.max(green,blue));
myMin = Math.min(red, Math.min(green,blue));
v = myMax;
if (myMax > 0) {
s = (myMax - myMin) / myMax;
}
else {
s = 0;
}
if (s > 0) {
myDiff = myMax - myMin;
rc = (myMax - red) / myDiff;
gc = (myMax - green) / myDiff;
bc = (myMax - blue) / myDiff;
if (red == myMax) {
h = (bc - gc) / 6;
}
if (green == myMax) {
h = (2 + rc - bc) / 6;
}
if (blue == myMax) {
h = (4 + gc - rc) / 6;
}
}
else {
h = 0;
}
if (h < 0) {
h += 1;
}
hsvArr["hue"] = Math.round(h * 360);
hsvArr["sat"] = s;
hsvArr["val"] = v;
return hsvArr;
}
//populate an array that can be addressed by both a key or an index nr
function assArrayPopulate(arrayKeys,arrayValues) {
var returnArray = new Array();
if (arrayKeys.length != arrayValues.length) {
alert("error: arrays do not have same length!");
}
else {
for (i=0;i<arrayKeys.length;i++) {
returnArray[arrayKeys[i]] = arrayValues[i];
}
}
return returnArray;
}
//replace special (non-ASCII) characters with their charCode
function replaceSpecialChars(myString) {
for (i=161;i<256;i++) {
re = new RegExp("&#"+i+";","g");
myString = myString.replace(re,String.fromCharCode(i));
}
return myString;
}
/* ----------------------- getXMLData object ----------------------------- */
/* ----------------------------------------------------------------------- */
//this object allows to make network requests using getURL or XMLHttpRequest
//you may specify a url and a callBackFunction
//the callBackFunction receives a XML node representing the rootElement of the fragment received
function getXMLData(url,callBackFunction) {
this.url = url;
this.callBackFunction = callBackFunction;
this.xmlRequest = null;
}
getXMLData.prototype.getData = function() {
//call getURL() if available
if (window.getURL) {
getURL(this.url,this);
}
//or call XMLHttpRequest() if available
else if (window.XMLHttpRequest) {
var _this = this;
this.xmlRequest = new XMLHttpRequest();
this.xmlRequest.overrideMimeType("text/xml");
this.xmlRequest.open("GET",this.url,true);
this.xmlRequest.onreadystatechange = function() {_this.handleEvent()};
this.xmlRequest.send(null);
}
//write an error message if neither method is available
else {
alert("your browser/svg viewer neither supports window.getURL nor window.XMLHttpRequest!");
}
}
//this is the callback method for the getURL function
getXMLData.prototype.operationComplete = function(data) {
//check if data has a success property
if (data.success) {
//parse content of the XML format to the variable "node"
var node = parseXML(data.content,document);
this.callBackFunction(node.firstChild);
}
else {
alert("something went wrong with dynamic loading of geometry!");
}
}
//this method receives data from XMLHttpRequest
getXMLData.prototype.handleEvent = function() {
if (this.xmlRequest.readyState == 4) {
var importedNode = document.importNode(this.xmlRequest.responseXML.documentElement,true);
this.callBackFunction(importedNode);
}
}
//starts an animation with the given id
//this function is useful in combination with window.setTimeout()
function startAnimation(id) {
document.getElementById(id).beginElement();
}