diff --git a/TODO.txt b/TODO.txt index 91b3fe81..79f1076d 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,10 +1,10 @@ -Potlatch 2: main outstanding issues ------------------------------------ += Potlatch 2: main outstanding issues = == Core geometry == * P1-style J (join) * Doing a '-' to remove a point from a junction doesn't redraw the point on other ways +* Circularise doesn't redraw the nodes on the selected way == Vector background layers == @@ -33,6 +33,7 @@ Potlatch 2: main outstanding issues ** Keypress detection is very erratic, depending on focus - need to work out where events are going (Application.application?) and sense them there * Failing to parse map_features should be an error state +* Quadrilateralise should throw an AttentionEvent if it's too sharp to straighten * Escape should rewind the entity to how it was before the current ControllerState. (Record a position in the undo stack when exiting a ControllerState, and escape would rewind to that) * Custom imagery dialog fixes * Ctrl-clicking two areas (one inside the other) should create a multipolygon @@ -72,6 +73,9 @@ Potlatch 2: main outstanding issues == l10n == * Fix the en_US / default locale problem +== other == +* Simplify.as shouldn't add an action to the global undo stack, since it's called from e.g. importing into other layers + Requested enhancements ---------------------- diff --git a/com/gradoservice/proj4as/Datum.as b/com/gradoservice/proj4as/Datum.as new file mode 100755 index 00000000..0632a67a --- /dev/null +++ b/com/gradoservice/proj4as/Datum.as @@ -0,0 +1,413 @@ +package com.gradoservice.proj4as +{ + import com.gradoservice.proj4as.proj.AbstractProjProjection; + import com.gradoservice.proj4as.ProjConstants; + + public class Datum + { + public var datum_type:int; + public var a:Number; + public var b:Number; + public var es:Number; + public var ep2:Number; + public var datum_params:Array; + public var params:Array; + + public function Datum(proj:ProjProjection) + { + this.datum_type = ProjConstants.PJD_WGS84; //default setting + if (proj.datumCode && proj.datumCode == 'none') + { + this.datum_type = ProjConstants.PJD_NODATUM; + } + if (proj && proj.datum_params) + { + for (var i:int=0; i 3) + { + if (proj.datum_params[3] != 0 || proj.datum_params[4] != 0 || + proj.datum_params[5] != 0 || proj.datum_params[6] != 0 ) + { + this.datum_type = ProjConstants.PJD_7PARAM; + proj.datum_params[3] *= ProjConstants.SEC_TO_RAD; + proj.datum_params[4] *= ProjConstants.SEC_TO_RAD; + proj.datum_params[5] *= ProjConstants.SEC_TO_RAD; + proj.datum_params[6] = (proj.datum_params[6]/1000000.0) + 1.0; + } + } + } + if (proj) + { + this.a = proj.a; //datum object also uses these values + this.b = proj.b; + this.es = proj.es; + this.ep2 = proj.ep2; + this.datum_params = proj.datum_params; + } + } + + /****************************************************************/ + // cs_compare_datums() + // Returns 1 (TRUE) if the two datums match, otherwise 0 (FALSE). + public function compare_datums( dest:Datum ):Boolean + { + if( this.datum_type != dest.datum_type ) + { + return false; // false, datums are not equal + } + else if( this.a != dest.a || Math.abs(this.es-dest.es) > 0.000000000050 ) + { + // the tolerence for es is to ensure that GRS80 and WGS84 + // are considered identical + return false; + } + else if( this.datum_type == ProjConstants.PJD_3PARAM ) + { + return (this.datum_params[0] == dest.datum_params[0] + && this.datum_params[1] == dest.datum_params[1] + && this.datum_params[2] == dest.datum_params[2]); + } else if( this.datum_type == ProjConstants.PJD_7PARAM ) + { + return (this.datum_params[0] == dest.datum_params[0] + && this.datum_params[1] == dest.datum_params[1] + && this.datum_params[2] == dest.datum_params[2] + && this.datum_params[3] == dest.datum_params[3] + && this.datum_params[4] == dest.datum_params[4] + && this.datum_params[5] == dest.datum_params[5] + && this.datum_params[6] == dest.datum_params[6]); + } + else if( this.datum_type == ProjConstants.PJD_GRIDSHIFT ) + { + /*return strcmp( pj_param(this.params,"snadgrids").s, + pj_param(dest.params,"snadgrids").s ) == 0; */ + return false; + } else + { + return true; // datums are equal + } + } // cs_compare_datums() + + + public function geodetic_to_geocentric(p:ProjPoint):int + { + var Longitude:Number = p.x; + var Latitude:Number = p.y; + var Height:Number = p.z ? p.z : 0; //Z value not always supplied + var X:Number; // output + var Y:Number; + var Z:Number; + + var Error_Code:int=0; // GEOCENT_NO_ERROR; + var Rn:Number; /* Earth radius at location */ + var Sin_Lat:Number; /* Math.sin(Latitude) */ + var Sin2_Lat:Number; /* Square of Math.sin(Latitude) */ + var Cos_Lat:Number; /* Math.cos(Latitude) */ + + /* + ** Don't blow up if Latitude is just a little out of the value + ** range as it may just be a rounding issue. Also removed longitude + ** test, it should be wrapped by Math.cos() and Math.sin(). NFW for PROJ.4, Sep/2001. + */ + if( Latitude < -ProjConstants.HALF_PI && Latitude > -1.001 *ProjConstants.HALF_PI ) { + Latitude = -ProjConstants.HALF_PI; + } else if( Latitude > ProjConstants.HALF_PI && Latitude < 1.001 * ProjConstants.HALF_PI ) { + Latitude = ProjConstants.HALF_PI; + } else if ((Latitude < -ProjConstants.HALF_PI) || (Latitude > ProjConstants.HALF_PI)) { + /* Latitude out of range */ + trace('geocent:lat out of range:'+Latitude); + return 0; + } + + if (Longitude > ProjConstants.PI) {Longitude -= (2*ProjConstants.PI);} + Sin_Lat = Math.sin(Latitude); + Cos_Lat = Math.cos(Latitude); + Sin2_Lat = Sin_Lat * Sin_Lat; + Rn = this.a / (Math.sqrt(1.0e0 - this.es * Sin2_Lat)); + X = (Rn + Height) * Cos_Lat * Math.cos(Longitude); + Y = (Rn + Height) * Cos_Lat * Math.sin(Longitude); + Z = ((Rn * (1 - this.es)) + Height) * Sin_Lat; + + p.x = X; + p.y = Y; + p.z = Z; + return Error_Code; + } // cs_geodetic_to_geocentric() + + + public function geocentric_to_geodetic(p:ProjPoint):ProjPoint + { + /* local defintions and variables */ + /* end-criterium of loop, accuracy of sin(Latitude) */ + var genau:Number = 1.E-12; + var genau2:Number = (genau*genau); + var maxiter:int = 30; + + var P:Number; /* distance between semi-minor axis and location */ + var RR:Number; /* distance between center and location */ + var CT:Number; /* sin of geocentric latitude */ + var ST:Number; /* cos of geocentric latitude */ + var RX:Number; + var RK:Number; + var RN:Number; /* Earth radius at location */ + var CPHI0:Number; /* cos of start or old geodetic latitude in iterations */ + var SPHI0:Number; /* sin of start or old geodetic latitude in iterations */ + var CPHI:Number; /* cos of searched geodetic latitude */ + var SPHI:Number; /* sin of searched geodetic latitude */ + var SDPHI:Number; /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */ + var At_Pole:Boolean; /* indicates location is in polar region */ + var iter:Number; /* # of continous iteration, max. 30 is always enough (s.a.) */ + + var X:Number = p.x; + var Y:Number = p.y; + var Z:Number = p.z ? p.z : 0.0; //Z value not always supplied + var Longitude:Number; + var Latitude:Number; + var Height:Number; + + At_Pole = false; + P = Math.sqrt(X*X+Y*Y); + RR = Math.sqrt(X*X+Y*Y+Z*Z); + +/* special cases for latitude and longitude */ + if (P/this.a < genau) + { + /* special case, if P=0. (X=0., Y=0.) */ + At_Pole = true; + Longitude = 0.0; +/* if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis + * of ellipsoid (=center of mass), Latitude becomes PI/2 */ + if (RR/this.a < genau) { + Latitude = ProjConstants.HALF_PI; + Height = -this.b; + return null; + } + } else { +/* ellipsoidal (geodetic) longitude + * interval: -PI < Longitude <= +PI */ + Longitude=Math.atan2(Y,X); + } + +/* -------------------------------------------------------------- + * Following iterative algorithm was developped by + * "Institut fьr Erdmessung", University of Hannover, July 1988. + * Internet: www.ife.uni-hannover.de + * Iterative computation of CPHI,SPHI and Height. + * Iteration of CPHI and SPHI to 10**-12 radian resp. + * 2*10**-7 arcsec. + * -------------------------------------------------------------- + */ + CT = Z/RR; + ST = P/RR; + RX = 1.0/Math.sqrt(1.0-this.es*(2.0-this.es)*ST*ST); + CPHI0 = ST*(1.0-this.es)*RX; + SPHI0 = CT*RX; + iter = 0; + +/* loop to find sin(Latitude) resp. Latitude + * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */ + do + { + iter++; + RN = this.a/Math.sqrt(1.0-this.es*SPHI0*SPHI0); + +/* ellipsoidal (geodetic) height */ + Height = P*CPHI0+Z*SPHI0-RN*(1.0-this.es*SPHI0*SPHI0); + + RK = this.es*RN/(RN+Height); + RX = 1.0/Math.sqrt(1.0-RK*(2.0-RK)*ST*ST); + CPHI = ST*(1.0-RK)*RX; + SPHI = CT*RX; + SDPHI = SPHI*CPHI0-CPHI*SPHI0; + CPHI0 = CPHI; + SPHI0 = SPHI; + } + while (SDPHI*SDPHI > genau2 && iter < maxiter); + +/* ellipsoidal (geodetic) latitude */ + Latitude=Math.atan(SPHI/Math.abs(CPHI)); + + p.x = Longitude; + p.y = Latitude; + p.z = Height; + return p; + } // cs_geocentric_to_geodetic() + + + public function geocentric_to_geodetic_noniter(p:ProjPoint):ProjPoint + { + var X:Number = p.x; + var Y:Number = p.y; + var Z:Number = p.z ? p.z : 0; //Z value not always supplied + var Longitude:Number; + var Latitude:Number; + var Height:Number; + + var W:Number; /* distance from Z axis */ + var W2:Number; /* square of distance from Z axis */ + var T0:Number; /* initial estimate of vertical component */ + var T1:Number; /* corrected estimate of vertical component */ + var S0:Number; /* initial estimate of horizontal component */ + var S1:Number; /* corrected estimate of horizontal component */ + var Sin_B0:Number; /* Math.sin(B0), B0 is estimate of Bowring aux variable */ + var Sin3_B0:Number; /* cube of Math.sin(B0) */ + var Cos_B0:Number; /* Math.cos(B0) */ + var Sin_p1:Number; /* Math.sin(phi1), phi1 is estimated latitude */ + var Cos_p1:Number; /* Math.cos(phi1) */ + var Rn:Number; /* Earth radius at location */ + var Sum:Number; /* numerator of Math.cos(phi1) */ + var At_Pole:Boolean; /* indicates location is in polar region */ + + At_Pole = false; + if (X != 0.0) + { + Longitude = Math.atan2(Y,X); + } + else + { + if (Y > 0) + { + Longitude = ProjConstants.HALF_PI; + } + else if (Y < 0) + { + Longitude = -ProjConstants.HALF_PI; + } + else + { + At_Pole = true; + Longitude = 0.0; + if (Z > 0.0) + { /* north pole */ + Latitude = ProjConstants.HALF_PI; + } + else if (Z < 0.0) + { /* south pole */ + Latitude = -ProjConstants.HALF_PI; + } + else + { /* center of earth */ + Latitude = ProjConstants.HALF_PI; + Height = -this.b; + return null; + } + } + } + W2 = X*X + Y*Y; + W = Math.sqrt(W2); + T0 = Z * ProjConstants.AD_C; + S0 = Math.sqrt(T0 * T0 + W2); + Sin_B0 = T0 / S0; + Cos_B0 = W / S0; + Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0; + T1 = Z + this.b * this.ep2 * Sin3_B0; + Sum = W - this.a * this.es * Cos_B0 * Cos_B0 * Cos_B0; + S1 = Math.sqrt(T1*T1 + Sum * Sum); + Sin_p1 = T1 / S1; + Cos_p1 = Sum / S1; + Rn = this.a / Math.sqrt(1.0 - this.es * Sin_p1 * Sin_p1); + if (Cos_p1 >= ProjConstants.COS_67P5) + { + Height = W / Cos_p1 - Rn; + } + else if (Cos_p1 <= -ProjConstants.COS_67P5) + { + Height = W / -Cos_p1 - Rn; + } + else + { + Height = Z / Sin_p1 + Rn * (this.es - 1.0); + } + if (At_Pole == false) + { + Latitude = Math.atan(Sin_p1 / Cos_p1); + } + + p.x = Longitude; + p.y = Latitude; + p.z = Height; + return p; + } // geocentric_to_geodetic_noniter() + + /****************************************************************/ + // pj_geocentic_to_wgs84( p ) + // p = point to transform in geocentric coordinates (x,y,z) + public function geocentric_to_wgs84( p:ProjPoint ):void + { + + if( this.datum_type == ProjConstants.PJD_3PARAM ) + { + // if( x[io] == HUGE_VAL ) + // continue; + p.x += this.datum_params[0]; + p.y += this.datum_params[1]; + p.z += this.datum_params[2]; + + } + else if (this.datum_type == ProjConstants.PJD_7PARAM) + { + var Dx_BF:Number =this.datum_params[0]; + var Dy_BF:Number =this.datum_params[1]; + var Dz_BF:Number =this.datum_params[2]; + var Rx_BF:Number =this.datum_params[3]; + var Ry_BF:Number =this.datum_params[4]; + var Rz_BF:Number =this.datum_params[5]; + var M_BF:Number =this.datum_params[6]; + // if( x[io] == HUGE_VAL ) + // continue; + var x_out:Number = M_BF*( p.x - Rz_BF*p.y + Ry_BF*p.z) + Dx_BF; + var y_out:Number = M_BF*( Rz_BF*p.x + p.y - Rx_BF*p.z) + Dy_BF; + var z_out:Number = M_BF*(-Ry_BF*p.x + Rx_BF*p.y + p.z) + Dz_BF; + p.x = x_out; + p.y = y_out; + p.z = z_out; + } + } // cs_geocentric_to_wgs84 + + /****************************************************************/ + // pj_geocentic_from_wgs84() + // coordinate system definition, + // point to transform in geocentric coordinates (x,y,z) + public function geocentric_from_wgs84( p:ProjPoint ):void + { + + if( this.datum_type == ProjConstants.PJD_3PARAM ) + { + //if( x[io] == HUGE_VAL ) + // continue; + p.x -= this.datum_params[0]; + p.y -= this.datum_params[1]; + p.z -= this.datum_params[2]; + + } + else if (this.datum_type == ProjConstants.PJD_7PARAM) + { + var Dx_BF:Number =this.datum_params[0]; + var Dy_BF:Number =this.datum_params[1]; + var Dz_BF:Number =this.datum_params[2]; + var Rx_BF:Number =this.datum_params[3]; + var Ry_BF:Number =this.datum_params[4]; + var Rz_BF:Number =this.datum_params[5]; + var M_BF:Number =this.datum_params[6]; + var x_tmp:Number = (p.x - Dx_BF) / M_BF; + var y_tmp:Number = (p.y - Dy_BF) / M_BF; + var z_tmp:Number = (p.z - Dz_BF) / M_BF; + //if( x[io] == HUGE_VAL ) + // continue; + + p.x = x_tmp + Rz_BF*y_tmp - Ry_BF*z_tmp; + p.y = -Rz_BF*x_tmp + y_tmp + Rx_BF*z_tmp; + p.z = Ry_BF*x_tmp - Rx_BF*y_tmp + z_tmp; + } //cs_geocentric_from_wgs84() + } + + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/LICENCE.txt b/com/gradoservice/proj4as/LICENCE.txt new file mode 100755 index 00000000..3ce7c3e2 --- /dev/null +++ b/com/gradoservice/proj4as/LICENCE.txt @@ -0,0 +1,10 @@ +Copyright (c) German Osin, 2009 +All rights reserved. +[see http://groups.google.com/group/openscales-dev/msg/425f736fd9d07884 for licence declaration] + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/com/gradoservice/proj4as/Proj4as.as b/com/gradoservice/proj4as/Proj4as.as new file mode 100755 index 00000000..745e3140 --- /dev/null +++ b/com/gradoservice/proj4as/Proj4as.as @@ -0,0 +1,157 @@ +package com.gradoservice.proj4as +{ + import com.gradoservice.proj4as.proj.AbstractProjProjection; + + public class Proj4as + { + + static public const defaultDatum:String = 'WGS84'; + static public const WGS84:ProjProjection = new ProjProjection('WGS84'); + + + public function Proj4as() + { + } + + public function transform(source:ProjProjection, dest:ProjProjection, point:ProjPoint):ProjPoint + { + + if (source==null || dest==null || point==null) + { + trace("Parametrs not created!"); + return null; + } + + if (!source.readyToUse || !dest.readyToUse) + { + trace("Proj4js initialization for "+source.srsCode+" not yet complete"); + return point; + } + + // Workaround for Spherical Mercator + if ((source.srsProjNumber =="900913" && dest.datumCode != "WGS84") || + (dest.srsProjNumber == "900913" && source.datumCode != "WGS84")) { + var wgs84:ProjProjection = WGS84; + this.transform(source, wgs84, point); + source = wgs84; + } + + // Transform source points to long/lat, if they aren't already. + if ( source.projName=="longlat") { + point.x *= ProjConstants.D2R; // convert degrees to radians + point.y *= ProjConstants.D2R; + } else { + if (source.to_meter) { + point.x *= source.to_meter; + point.y *= source.to_meter; + } + source.inverse(point); // Convert Cartesian to longlat + } + + // Adjust for the prime meridian if necessary + if (source.from_greenwich) { + point.x += source.from_greenwich; + } + + // Convert datums if needed, and if possible. + point = this.datum_transform( source.datum, dest.datum, point ); + + // Adjust for the prime meridian if necessary + if (dest.from_greenwich) { + point.x -= dest.from_greenwich; + } + + if( dest.projName=="longlat" ) { + // convert radians to decimal degrees + point.x *= ProjConstants.R2D; + point.y *= ProjConstants.R2D; + } else { // else project + dest.forward(point); + if (dest.to_meter) { + point.x /= dest.to_meter; + point.y /= dest.to_meter; + } + } + return point; + } + + + public function datum_transform( source:Datum, dest:Datum, point:ProjPoint ):ProjPoint + { + + // Short cut if the datums are identical. + if( source.compare_datums( dest ) ) { + return point; // in this case, zero is sucess, + // whereas cs_compare_datums returns 1 to indicate TRUE + // confusing, should fix this + } + + // Explicitly skip datum transform by setting 'datum=none' as parameter for either source or dest + if( source.datum_type == ProjConstants.PJD_NODATUM + || dest.datum_type == ProjConstants.PJD_NODATUM) { + return point; + } + + // If this datum requires grid shifts, then apply it to geodetic coordinates. + if( source.datum_type == ProjConstants.PJD_GRIDSHIFT ) + { + trace("ERROR: Grid shift transformations are not implemented yet."); + /* + pj_apply_gridshift( pj_param(source.params,"snadgrids").s, 0, + point_count, point_offset, x, y, z ); + CHECK_RETURN; + + src_a = SRS_WGS84_SEMIMAJOR; + src_es = 0.006694379990; + */ + } + + if( dest.datum_type == ProjConstants.PJD_GRIDSHIFT ) + { + trace("ERROR: Grid shift transformations are not implemented yet."); + /* + dst_a = ; + dst_es = 0.006694379990; + */ + } + + // Do we need to go through geocentric coordinates? + if( source.es != dest.es || source.a != dest.a + || source.datum_type == ProjConstants.PJD_3PARAM + || source.datum_type == ProjConstants.PJD_7PARAM + || dest.datum_type == ProjConstants.PJD_3PARAM + || dest.datum_type == ProjConstants.PJD_7PARAM) + { + + // Convert to geocentric coordinates. + source.geodetic_to_geocentric( point ); + // CHECK_RETURN; + + // Convert between datums + if( source.datum_type == ProjConstants.PJD_3PARAM || source.datum_type == ProjConstants.PJD_7PARAM ) { + source.geocentric_to_wgs84(point); + // CHECK_RETURN; + } + + if( dest.datum_type == ProjConstants.PJD_3PARAM || dest.datum_type == ProjConstants.PJD_7PARAM ) { + dest.geocentric_from_wgs84(point); + // CHECK_RETURN; + } + + // Convert back to geodetic coordinates + dest.geocentric_to_geodetic( point ); + // CHECK_RETURN; + } + + // Apply grid shift to destination if required + if( dest.datum_type == ProjConstants.PJD_GRIDSHIFT ) + { + trace("ERROR: Grid shift transformations are not implemented yet."); + // pj_apply_gridshift( pj_param(dest.params,"snadgrids").s, 1, point); + // CHECK_RETURN; + } + return point; + } + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/ProjConstants.as b/com/gradoservice/proj4as/ProjConstants.as new file mode 100755 index 00000000..d4be0768 --- /dev/null +++ b/com/gradoservice/proj4as/ProjConstants.as @@ -0,0 +1,288 @@ +package com.gradoservice.proj4as +{ + public class ProjConstants + { + + static public const PI:Number=3.141592653589793238; //Math.PI, + static public const HALF_PI:Number=1.570796326794896619; //Math.PI*0.5, + static public const TWO_PI:Number=6.283185307179586477; //Math.PI*2, + static public const FORTPI:Number=0.78539816339744833; + static public const R2D:Number=57.29577951308232088; + static public const D2R:Number=0.01745329251994329577; + static public const SEC_TO_RAD:Number=4.84813681109535993589914102357e-6; /* SEC_TO_RAD = Pi/180/3600 */ + static public const EPSLN:Number=1.0e-10; + static public const MAX_ITER:Number=20; + // following constants from geocent.c + static public const COS_67P5:Number=0.38268343236508977; /* cosine of 67.5 degrees */ + static public const AD_C:Number=1.0026000; /* Toms region 1 constant */ + + /* datum_type values */ + static public const PJD_UNKNOWN:int=0; + static public const PJD_3PARAM:int=1; + static public const PJD_7PARAM:int=2; + static public const PJD_GRIDSHIFT:int=3; + static public const PJD_WGS84:int=4; // WGS84 or equivalent + static public const PJD_NODATUM:int=5; // WGS84 or equivalent + static public const SRS_WGS84_SEMIMAJOR:int=6378137; // only used in grid shift transforms + + // ellipoid pj_set_ell.c + static public const SIXTH :Number=0.1666666666666666667; /* 1/6 */ + static public const RA4:Number=0.04722222222222222222; /* 17/360 */ + static public const RA6:Number=0.02215608465608465608; /* 67/3024 */ + static public const RV4:Number=0.06944444444444444444; /* 5/72 */ + static public const RV6:Number=0.04243827160493827160; /* 55/1296 */ + + + + static public const PrimeMeridian:Object = { + "greenwich": 0.0, //"0dE", + "lisbon": -9.131906111111, //"9d07'54.862\"W", + "paris": 2.337229166667, //"2d20'14.025\"E", + "bogota": -74.080916666667, //"74d04'51.3\"W", + "madrid": -3.687938888889, //"3d41'16.58\"W", + "rome": 12.452333333333, //"12d27'8.4\"E", + "bern": 7.439583333333, //"7d26'22.5\"E", + "jakarta": 106.807719444444, //"106d48'27.79\"E", + "ferro": -17.666666666667, //"17d40'W", + "brussels": 4.367975, //"4d22'4.71\"E", + "stockholm": 18.058277777778, //"18d3'29.8\"E", + "athens": 23.7163375, //"23d42'58.815\"E", + "oslo": 10.722916666667 //"10d43'22.5\"E" + }; + + static public const Ellipsoid:Object = { + "MERIT": {a:6378137.0, rf:298.257, ellipseName:"MERIT 1983"}, + "SGS85": {a:6378136.0, rf:298.257, ellipseName:"Soviet Geodetic System 85"}, + "GRS80": {a:6378137.0, rf:298.257222101, ellipseName:"GRS 1980(IUGG, 1980)"}, + "IAU76": {a:6378140.0, rf:298.257, ellipseName:"IAU 1976"}, + "airy": {a:6377563.396, b:6356256.910, ellipseName:"Airy 1830"}, + "APL4.": {a:6378137, rf:298.25, ellipseName:"Appl. Physics. 1965"}, + "NWL9D": {a:6378145.0, rf:298.25, ellipseName:"Naval Weapons Lab., 1965"}, + "mod_airy": {a:6377340.189, b:6356034.446, ellipseName:"Modified Airy"}, + "andrae": {a:6377104.43, rf:300.0, ellipseName:"Andrae 1876 (Den., Iclnd.)"}, + "aust_SA": {a:6378160.0, rf:298.25, ellipseName:"Australian Natl & S. Amer. 1969"}, + "GRS67": {a:6378160.0, rf:298.2471674270, ellipseName:"GRS 67(IUGG 1967)"}, + "bessel": {a:6377397.155, rf:299.1528128, ellipseName:"Bessel 1841"}, + "bess_nam": {a:6377483.865, rf:299.1528128, ellipseName:"Bessel 1841 (Namibia)"}, + "clrk66": {a:6378206.4, b:6356583.8, ellipseName:"Clarke 1866"}, + "clrk80": {a:6378249.145, rf:293.4663, ellipseName:"Clarke 1880 mod."}, + "CPM": {a:6375738.7, rf:334.29, ellipseName:"Comm. des Poids et Mesures 1799"}, + "delmbr": {a:6376428.0, rf:311.5, ellipseName:"Delambre 1810 (Belgium)"}, + "engelis": {a:6378136.05, rf:298.2566, ellipseName:"Engelis 1985"}, + "evrst30": {a:6377276.345, rf:300.8017, ellipseName:"Everest 1830"}, + "evrst48": {a:6377304.063, rf:300.8017, ellipseName:"Everest 1948"}, + "evrst56": {a:6377301.243, rf:300.8017, ellipseName:"Everest 1956"}, + "evrst69": {a:6377295.664, rf:300.8017, ellipseName:"Everest 1969"}, + "evrstSS": {a:6377298.556, rf:300.8017, ellipseName:"Everest (Sabah & Sarawak)"}, + "fschr60": {a:6378166.0, rf:298.3, ellipseName:"Fischer (Mercury Datum) 1960"}, + "fschr60m": {a:6378155.0, rf:298.3, ellipseName:"Fischer 1960"}, + "fschr68": {a:6378150.0, rf:298.3, ellipseName:"Fischer 1968"}, + "helmert": {a:6378200.0, rf:298.3, ellipseName:"Helmert 1906"}, + "hough": {a:6378270.0, rf:297.0, ellipseName:"Hough"}, + "intl": {a:6378388.0, rf:297.0, ellipseName:"International 1909 (Hayford)"}, + "kaula": {a:6378163.0, rf:298.24, ellipseName:"Kaula 1961"}, + "lerch": {a:6378139.0, rf:298.257, ellipseName:"Lerch 1979"}, + "mprts": {a:6397300.0, rf:191.0, ellipseName:"Maupertius 1738"}, + "new_intl": {a:6378157.5, b:6356772.2, ellipseName:"New International 1967"}, + "plessis": {a:6376523.0, rf:6355863.0, ellipseName:"Plessis 1817 (France)"}, + "krass": {a:6378245.0, rf:298.3, ellipseName:"Krassovsky, 1942"}, + "SEasia": {a:6378155.0, b:6356773.3205, ellipseName:"Southeast Asia"}, + "walbeck": {a:6376896.0, b:6355834.8467, ellipseName:"Walbeck"}, + "WGS60": {a:6378165.0, rf:298.3, ellipseName:"WGS 60"}, + "WGS66": {a:6378145.0, rf:298.25, ellipseName:"WGS 66"}, + "WGS72": {a:6378135.0, rf:298.26, ellipseName:"WGS 72"}, + "WGS84": {a:6378137.0, rf:298.257223563, ellipseName:"WGS 84"}, + "sphere": {a:6370997.0, b:6370997.0, ellipseName:"Normal Sphere (r=6370997)"} +}; + + static public const Datum:Object = { + "WGS84": {towgs84: "0,0,0", ellipse: "WGS84", datumName: "WGS84"}, + "GGRS87": {towgs84: "-199.87,74.79,246.62", ellipse: "GRS80", datumName: "Greek_Geodetic_Reference_System_1987"}, + "NAD83": {towgs84: "0,0,0", ellipse: "GRS80", datumName: "North_American_Datum_1983"}, + "NAD27": {nadgrids: "@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat", ellipse: "clrk66", datumName: "North_American_Datum_1927"}, + "potsdam": {towgs84: "606.0,23.0,413.0", ellipse: "bessel", datumName: "Potsdam Rauenberg 1950 DHDN"}, + "carthage": {towgs84: "-263.0,6.0,431.0", ellipse: "clark80", datumName: "Carthage 1934 Tunisia"}, + "hermannskogel": {towgs84: "653.0,-212.0,449.0", ellipse: "bessel", datumName: "Hermannskogel"}, + "ire65": {towgs84: "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", ellipse: "mod_airy", datumName: "Ireland 1965"}, + "nzgd49": {towgs84: "59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", ellipse: "intl", datumName: "New Zealand Geodetic Datum 1949"}, + "OSGB36": {towgs84: "446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", ellipse: "airy", datumName: "Airy 1830"} +}; + + + + public function ProjConstants() + { + } + + + // Function to compute the constant small m which is the radius of + // a parallel of latitude, phi, divided by the semimajor axis. + // ----------------------------------------------------------------- + static public function msfnz (eccent:Number, sinphi:Number, cosphi:Number):Number + { + var con:Number = eccent * sinphi; + return cosphi/(Math.sqrt(1.0 - con * con)); + } + + // Function to compute the constant small t for use in the forward + // computations in the Lambert Conformal Conic and the Polar + // Stereographic projections. + // ----------------------------------------------------------------- + static public function tsfnz(eccent:Number, phi:Number, sinphi:Number):Number + { + var con:Number = eccent * sinphi; + var com:Number = 0.5 * eccent; + con = Math.pow(((1.0 - con) / (1.0 + con)), com); + return (Math.tan(0.5 * (ProjConstants.HALF_PI - phi))/con); + } + + // Function to compute the latitude angle, phi2, for the inverse of the + // Lambert Conformal Conic and Polar Stereographic projections. + // ---------------------------------------------------------------- + static public function phi2z(eccent:Number, ts:Number):Number + { + var eccnth:Number = .5 * eccent; + var con:Number =0; + var dphi:Number =0; + var phi:Number = ProjConstants.HALF_PI - 2 * Math.atan(ts); + for (var i:int = 0; i <= 15; i++) { + con = eccent * Math.sin(phi); + dphi = ProjConstants.HALF_PI - 2 * Math.atan(ts *(Math.pow(((1.0 - con)/(1.0 + con)),eccnth))) - phi; + phi += dphi; + if (Math.abs(dphi) <= .0000000001) return phi; + } + trace("phi2z has NoConvergence"); + return -9999; + } + + /* Function to compute constant small q which is the radius of a + parallel of latitude, phi, divided by the semimajor axis. + ------------------------------------------------------------*/ + static public function qsfnz(eccent:Number,sinphi:Number,cosphi:Number):Number + { + var con:Number = 0; + if (eccent > 1.0e-7) { + con = eccent * sinphi; + return (( 1.0- eccent * eccent) * (sinphi /(1.0 - con * con) - (.5/eccent)*Math.log((1.0 - con)/(1.0 + con)))); + } else { + return 2.0 * sinphi; + } + } + + /* Function to eliminate roundoff errors in asin + ----------------------------------------------*/ + static public function asinz(x:Number):Number + { + if (Math.abs(x)>1.0) { + x=(x>1.0)?1.0:-1.0; + } + return Math.asin(x); + } + + // following functions from gctpc cproj.c for transverse mercator projections + static public function e0fn(x:Number):Number {return(1.0-0.25*x*(1.0+x/16.0*(3.0+1.25*x)));} + static public function e1fn(x:Number):Number {return(0.375*x*(1.0+0.25*x*(1.0+0.46875*x)));} + static public function e2fn(x:Number):Number {return(0.05859375*x*x*(1.0+0.75*x));} + static public function e3fn(x:Number):Number {return(x*x*x*(35.0/3072.0));} + static public function mlfn(e0:Number,e1:Number,e2:Number,e3:Number,phi:Number):Number {return(e0*phi-e1*Math.sin(2.0*phi)+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi));} + + static public function srat(esinp:Number, exp:Number):Number + { + return(Math.pow((1.0-esinp)/(1.0+esinp), exp)); + } + +// Function to return the sign of an argument + static public function sign(x:Number):Number { if (x < 0.0) return(-1); else return(1);} + +// Function to adjust longitude to -180 to 180; input in radians + static public function adjust_lon(x:Number):Number { + x = (Math.abs(x) < ProjConstants.PI) ? x: (x - (ProjConstants.sign(x)*ProjConstants.TWO_PI) ); + return x; + } + +// IGNF - DGR : algorithms used by IGN France + +// Function to adjust latitude to -90 to 90; input in radians + static public function adjust_lat(x:Number):Number { + x= (Math.abs(x) < ProjConstants.HALF_PI) ? x: (x - (ProjConstants.sign(x)*ProjConstants.PI) ); + return x; + } + +// Latitude Isometrique - close to tsfnz ... + static public function latiso(eccent:Number, phi:Number, sinphi:Number):Number + { + if (Math.abs(phi) > ProjConstants.HALF_PI) return +Number.NaN; + if (phi==ProjConstants.HALF_PI) return Number.POSITIVE_INFINITY; + if (phi==-1.0*ProjConstants.HALF_PI) return -1.0*Number.POSITIVE_INFINITY; + + var con:Number= eccent*sinphi; + return Math.log(Math.tan((ProjConstants.HALF_PI+phi)/2.0))+eccent*Math.log((1.0-con)/(1.0+con))/2.0; + } + + static public function fL(x:Number,L:Number):Number { + return 2.0*Math.atan(x*Math.exp(L)) - ProjConstants.HALF_PI; + } + +// Inverse Latitude Isometrique - close to ph2z + static public function invlatiso(eccent:Number, ts:Number):Number { + var phi:Number= ProjConstants.fL(1.0,ts); + var Iphi:Number= 0.0; + var con:Number= 0.0; + do { + Iphi= phi; + con= eccent*Math.sin(Iphi); + phi= ProjConstants.fL(Math.exp(eccent*Math.log((1.0+con)/(1.0-con))/2.0),ts) + } while (Math.abs(phi-Iphi)>1.0e-12); + return phi; + } + +// Needed for Gauss Laborde +// Original: Denis Makarov (info@binarythings.com) +// Web Site: http://www.binarythings.com + static public function sinh(x:Number):Number + { + var r:Number= Math.exp(x); + r= (r-1.0/r)/2.0; + return r; + } + + static public function cosh(x:Number):Number + { + var r:Number= Math.exp(x); + r= (r+1.0/r)/2.0; + return r; + } + + static public function tanh(x:Number):Number + { + var r:Number= Math.exp(x); + r= (r-1.0/r)/(r+1.0/r); + return r; + } + + static public function asinh(x:Number):Number + { + var s:Number= (x>= 0? 1.0:-1.0); + return s*(Math.log( Math.abs(x) + Math.sqrt(x*x+1.0) )); + } + + static public function acosh(x:Number):Number + { + return 2.0*Math.log(Math.sqrt((x+1.0)/2.0) + Math.sqrt((x-1.0)/2.0)); + } + + static public function atanh(x:Number):Number + { + return Math.log((x-1.0)/(x+1.0))/2.0; + } + +// Grande Normale + static public function gN(a:Number,e:Number,sinphi:Number):Number + { + var temp:Number= e*sinphi; + return a/Math.sqrt(1.0 - temp*temp); + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/ProjPoint.as b/com/gradoservice/proj4as/ProjPoint.as new file mode 100755 index 00000000..f56dc1b4 --- /dev/null +++ b/com/gradoservice/proj4as/ProjPoint.as @@ -0,0 +1,50 @@ +package com.gradoservice.proj4as +{ + public class ProjPoint + { + public var x:Number; + public var y:Number; + public var z:Number; + + public function ProjPoint(x:Number,y:Number,z:Number) + { + this.x = x; + this.y = y; + this.z = z; + } + + public function clone():ProjPoint + { + return new ProjPoint(this.x, this.y, this.z); + } + + /** + * APIMethod: toString + * Return a readable string version of the point + * + * Return: + * {String} String representation of Proj4js.Point object. + * (ex. "x=5,y=42") + */ + public function toString():String + { + return ("x=" + this.x + ",y=" + this.y); + } + + /** + * APIMethod: toShortString + * Return a short string version of the point. + * + * Return: + * {String} Shortened String representation of Proj4js.Point object. + * (ex. "5, 42") + */ + public function toShortString ():String + { + return (this.x + ", " + this.y); + } + + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/ProjProjection.as b/com/gradoservice/proj4as/ProjProjection.as new file mode 100755 index 00000000..9b0a2906 --- /dev/null +++ b/com/gradoservice/proj4as/ProjProjection.as @@ -0,0 +1,282 @@ +package com.gradoservice.proj4as +{ + import com.gradoservice.proj4as.proj.*; + + public class ProjProjection + { + /** + * Property: readyToUse + * Flag to indicate if initialization is complete for this Proj object + */ + public var readyToUse:Boolean = false; + + /** + * Property: title + * The title to describe the projection + */ + protected var projParams:ProjParams = new ProjParams(); + + static public const defs:Object = { + 'EPSG:900913': "+title=Google Mercator EPSG:900913 +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs", + 'WGS84': "+title=long/lat:WGS84 +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees", + 'EPSG:4326': "+title=long/lat:WGS84 +proj=longlat +a=6378137.0 +b=6356752.31424518 +ellps=WGS84 +datum=WGS84 +units=degrees", + 'EPSG:4269': "+title=long/lat:NAD83 +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees", + 'EPSG:32639': "+title=WGS 84 / UTM zone 39N +proj=utm +zone=39 +ellps=WGS84 +datum=WGS84 +units=m +no_defs", + 'EPSG:27700': "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs" + } + + + + protected var proj:IProjection; + + public function get srsCode():String + { + return projParams.srsCode; + } + + public function get srsProjNumber():String + { + return projParams.srsProjNumber; + } + + public function get projName():String + { + return projParams.projName; + } + + public function get datum():Datum + { + return projParams.datum; + } + + public function get datumCode():String + { + return projParams.datumCode; + } + + public function get from_greenwich():Number + { + return projParams.from_greenwich; + } + + public function get to_meter():Number + { + return projParams.to_meter; + } + + public function get a():Number + { + return projParams.a; + } + + public function get b():Number + { + return projParams.b; + } + + public function get ep2():Number + { + return projParams.ep2; + } + + public function get es():Number + { + return projParams.es; + } + + public function get datum_params():Array + { + return projParams.datum_params; + } + + public function ProjProjection(srsCode:String) + { + this.projParams.srsCode = srsCode.toUpperCase(); + + if (this.projParams.srsCode.indexOf("EPSG") == 0) { + this.projParams.srsAuth = 'epsg'; + this.projParams.srsProjNumber = this.projParams.srsCode.substring(5); + // DGR 2007-11-20 : authority IGNF + } else if (this.projParams.srsCode.indexOf("IGNF") == 0) { + this.projParams.srsAuth = 'IGNF'; + this.projParams.srsProjNumber = this.projParams.srsCode.substring(5); + // DGR 2008-06-19 : pseudo-authority CRS for WMS + } else if (this.projParams.srsCode.indexOf("CRS") == 0) { + this.projParams.srsAuth = 'CRS'; + this.projParams.srsProjNumber = this.projParams.srsCode.substring(4); + } else { + this.projParams.srsAuth = ''; + this.projParams.srsProjNumber = this.projParams.srsCode; + } + this.loadProjDefinition(); + } + + private function loadProjDefinition():void + { + if (this.srsCode!=null && ProjProjection.defs[this.srsCode]!=null) + { + this.parseDef(ProjProjection.defs[this.projParams.srsCode]); + this.initTransforms(); + } + } + + protected function initTransforms():void + { + switch (this.projParams.projName) + { + case "aea": this.proj = new ProjAea(this.projParams); break; + case "aeqd": this.proj = new ProjAeqd(this.projParams); break; + case "eqc": this.proj = new ProjEqc(this.projParams); break; + case "eqdc": this.proj = new ProjEqdc(this.projParams); break; + case "equi": this.proj = new ProjEqui(this.projParams); break; + case "gauss": this.proj = new ProjGauss(this.projParams); break; + case "gstmerc": this.proj = new ProjGstmerc(this.projParams); break; + case "laea": this.proj = new ProjLaea(this.projParams); break; + case "lcc": this.proj = new ProjLcc(this.projParams); break; + case "longlat": this.proj = new ProjLonglat(this.projParams); break; + case "merc": this.proj = new ProjMerc(this.projParams); break; + case "mill": this.proj = new ProjMill(this.projParams); break; + case "moll": this.proj = new ProjMoll(this.projParams); break; + case "nzmg": this.proj = new ProjNzmg(this.projParams); break; + case "omerc": this.proj = new ProjOmerc(this.projParams); break; + case "ortho": this.proj = new ProjOrtho(this.projParams); break; + case "sinu": this.proj = new ProjSinu(this.projParams); break; + case "omerc": this.proj = new ProjOmerc(this.projParams); break; + case "stere": this.proj = new ProjStere(this.projParams); break; + case "sterea": this.proj = new ProjSterea(this.projParams); break; + case "tmerc": this.proj = new ProjTmerc(this.projParams); break; + case "utm": this.proj = new ProjUtm(this.projParams); break; + case "vandg": this.proj = new ProjVandg(this.projParams); break; + } + if (this.proj!=null) { + this.proj.init(); + this.readyToUse = true; + } + } + + private function parseDef(definition:String):void + { + var paramName:String = ''; + var paramVal:String = ''; + var paramArray:Array=definition.split("+"); + for (var prop:int=0; prop ProjConstants.EPSLN) { + this.ns0 = (this.ms1 * this.ms1 - this.ms2 *this.ms2)/ (this.qs2 - this.qs1); + } else { + this.ns0 = this.con; + } + this.c = this.ms1 * this.ms1 + this.ns0 * this.qs1; + this.rh = this.a * Math.sqrt(this.c - this.ns0 * this.qs0)/this.ns0; + } + +/* Albers Conical Equal Area forward equations--mapping lat,long to x,y + -------------------------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number=p.x; + var lat:Number=p.y; + + this.sin_phi=Math.sin(lat); + this.cos_phi=Math.cos(lat); + + var qs:Number = ProjConstants.qsfnz(this.e3,this.sin_phi,this.cos_phi); + var rh1:Number =this.a * Math.sqrt(this.c - this.ns0 * qs)/this.ns0; + var theta:Number = this.ns0 * ProjConstants.adjust_lon(lon - this.long0); + var x:Number = rh1 * Math.sin(theta) + this.x0; + var y:Number = this.rh - rh1 * Math.cos(theta) + this.y0; + + p.x = x; + p.y = y; + return p; + } + + + override public function inverse(p:ProjPoint):ProjPoint + { + var rh1:Number; + var qs:Number; + var con:Number; + var theta:Number + var lon:Number; + var lat:Number; + + p.x -= this.x0; + p.y = this.rh - p.y + this.y0; + if (this.ns0 >= 0) { + rh1 = Math.sqrt(p.x *p.x + p.y * p.y); + con = 1.0; + } else { + rh1 = -Math.sqrt(p.x * p.x + p.y *p.y); + con = -1.0; + } + theta = 0.0; + if (rh1 != 0.0) { + theta = Math.atan2(con * p.x, con * p.y); + } + con = rh1 * this.ns0 / this.a; + qs = (this.c - con * con) / this.ns0; + if (this.e3 >= 1e-10) { + con = 1 - .5 * (1.0 -this.es) * Math.log((1.0 - this.e3) / (1.0 + this.e3))/this.e3; + if (Math.abs(Math.abs(con) - Math.abs(qs)) > .0000000001 ) { + lat = this.phi1z(this.e3,qs); + } else { + if (qs >= 0) { + lat = .5 * Math.PI; + } else { + lat = -.5 * Math.PI; + } + } + } else { + lat = this.phi1z(e3,qs); + } + + lon = ProjConstants.adjust_lon(theta/this.ns0 + this.long0); + p.x = lon; + p.y = lat; + return p; + } + +/* Function to compute phi1, the latitude for the inverse of the + Albers Conical Equal-Area projection. +-------------------------------------------*/ + private function phi1z (eccent:Number,qs:Number):Number + { + var con:Number; + var com:Number + var dphi:Number; + var phi:Number = ProjConstants.asinz(.5 * qs); + if (eccent < ProjConstants.EPSLN) return phi; + + var eccnts:Number = eccent * eccent; + for (var i:int = 1; i <= 25; i++) { + sinphi = Math.sin(phi); + cosphi = Math.cos(phi); + con = eccent * sinphi; + com = 1.0 - con * con; + dphi = .5 * com * com / cosphi * (qs / (1.0 - eccnts) - sinphi / com + .5 / eccent * Math.log((1.0 - con) / (1.0 + con))); + phi = phi + dphi; + if (Math.abs(dphi) <= 1e-7) return phi; + } + trace("aea:phi1z:Convergence error"); + return 0; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjAeqd.as b/com/gradoservice/proj4as/proj/ProjAeqd.as new file mode 100755 index 00000000..408470a4 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjAeqd.as @@ -0,0 +1,93 @@ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjAeqd extends AbstractProjProjection + { + public function ProjAeqd(data:ProjParams) + { + super(data); + } + + override public function init():void + { + this.sin_p12=Math.sin(this.lat0) + this.cos_p12=Math.cos(this.lat0) + } + + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number=p.x; + var lat:Number=p.y; + var ksp:Number; + + var sinphi:Number=Math.sin(p.y); + var cosphi:Number=Math.cos(p.y); + var dlon:Number = ProjConstants.adjust_lon(lon - this.long0); + var coslon:Number = Math.cos(dlon); + var g:Number = this.sin_p12 * sinphi + this.cos_p12 * cosphi * coslon; + if (Math.abs(Math.abs(g) - 1.0) < ProjConstants.EPSLN) { + ksp = 1.0; + if (g < 0.0) { + trace("aeqd:Fwd:PointError"); + return null; + } + } else { + var z:Number = Math.acos(g); + ksp = z/Math.sin(z); + } + p.x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon); + p.y = this.y0 + this.a * ksp * (this.cos_p12 * sinphi - this.sin_p12 * cosphi * coslon); + return p; + } + + override public function inverse(p:ProjPoint):ProjPoint + { + p.x -= this.x0; + p.y -= this.y0; + + var rh:Number = Math.sqrt(p.x * p.x + p.y *p.y); + if (rh > (2.0 * ProjConstants.HALF_PI * this.a)) + { + trace("aeqdInvDataError"); + return null; + } + var z:Number = rh / this.a; + + var sinz:Number=Math.sin(z) + var cosz:Number=Math.cos(z) + + var lon:Number = this.long0; + var lat:Number; + if (Math.abs(rh) <= ProjConstants.EPSLN) { + lat = this.lat0; + } else { + lat = ProjConstants.asinz(cosz * this.sin_p12 + (p.y * sinz * this.cos_p12) / rh); + var con:Number = Math.abs(this.lat0) - ProjConstants.HALF_PI; + if (Math.abs(con) <= ProjConstants.EPSLN) { + if (lat0 >= 0.0) { + lon = ProjConstants.adjust_lon(this.long0 + Math.atan2(p.x , -p.y)); + } else { + lon = ProjConstants.adjust_lon(this.long0 - Math.atan2(-p.x , p.y)); + } + } else { + con = cosz - this.sin_p12 * Math.sin(lat); + if ((Math.abs(con) < ProjConstants.EPSLN) && (Math.abs(p.x) < ProjConstants.EPSLN)) { + //no-op, just keep the lon value as is + } else { + var temp:Number = Math.atan2((p.x * sinz * this.cos_p12), (con * rh)); + lon = ProjConstants.adjust_lon(this.long0 + Math.atan2((p.x * sinz * this.cos_p12), (con * rh))); + } + } + } + + p.x = lon; + p.y = lat; + return p; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjEqc.as b/com/gradoservice/proj4as/proj/ProjEqc.as new file mode 100755 index 00000000..18e8315b --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjEqc.as @@ -0,0 +1,55 @@ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjEqc extends AbstractProjProjection + { + public function ProjEqc(data:ProjParams) + { + super(data); + } + + override public function init():void + { + if(!this.x0) this.x0=0; + if(!this.y0) this.y0=0; + if(!this.lat0) this.lat0=0; + if(!this.long0) this.long0=0; + if(!this.lat_ts) this.lat_ts=0; + if (!this.title) this.title = "Equidistant Cylindrical (Plate Carre)"; + this.rc= Math.cos(this.lat_ts); + } + + + // forward equations--mapping lat,long to x,y + // ----------------------------------------------------------------- + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number= p.x; + var lat:Number= p.y; + + var dlon:Number = ProjConstants.adjust_lon(lon - this.long0); + var dlat:Number = ProjConstants.adjust_lat(lat - this.lat0 ); + p.x= this.x0 + (this.a*dlon*this.rc); + p.y= this.y0 + (this.a*dlat ); + return p; + } + + // inverse equations--mapping x,y to lat/long + // ----------------------------------------------------------------- + override public function inverse(p:ProjPoint):ProjPoint + { + var x:Number= p.x; + var y:Number= p.y; + + p.x= ProjConstants.adjust_lon(this.long0 + ((x - this.x0)/(this.a*this.rc))); + p.y= ProjConstants.adjust_lat(this.lat0 + ((y - this.y0)/(this.a ))); + return p; + } + + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjEqdc.as b/com/gradoservice/proj4as/proj/ProjEqdc.as new file mode 100755 index 00000000..0d7a6a32 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjEqdc.as @@ -0,0 +1,156 @@ +/******************************************************************************* +NAME EQUIDISTANT CONIC + +PURPOSE: Transforms input longitude and latitude to Easting and Northing + for the Equidistant Conic projection. The longitude and + latitude must be in radians. The Easting and Northing values + will be returned in meters. + +PROGRAMMER DATE +---------- ---- +T. Mittan Mar, 1993 + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government + Printing Office, Washington D.C., 1989. +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjEqdc extends AbstractProjProjection + { + public function ProjEqdc(data:ProjParams) + { + super(data); + } + + +/* Variables common to all subroutines in this code file + -----------------------------------------------------*/ + +/* Initialize the Equidistant Conic projection + ------------------------------------------*/ + override public function init():void + { + /* Place parameters in static storage for common use + -------------------------------------------------*/ + if(!this.mode) this.mode=0;//chosen default mode + this.temp = this.b / this.a; + this.es = 1.0 - Math.pow(this.temp,2); + this.e = Math.sqrt(this.es); + this.e0 = ProjConstants.e0fn(this.es); + this.e1 = ProjConstants.e1fn(this.es); + this.e2 = ProjConstants.e2fn(this.es); + this.e3 = ProjConstants.e3fn(this.es); + + this.sinphi=Math.sin(this.lat1); + this.cosphi=Math.cos(this.lat1); + + this.ms1 = ProjConstants.msfnz(this.e,this.sinphi,this.cosphi); + this.ml1 = ProjConstants.mlfn(this.e0, this.e1, this.e2,this.e3, this.lat1); + + /* format B + ---------*/ + if (this.mode != 0) { + if (Math.abs(this.lat1 + this.lat2) < ProjConstants.EPSLN) { + trace("eqdc:Init:EqualLatitudes"); + //return(81); + } + this.sinphi=Math.sin(this.lat2); + this.cosphi=Math.cos(this.lat2); + + this.ms2 = ProjConstants.msfnz(this.e,this.sinphi,this.cosphi); + this.ml2 = ProjConstants.mlfn(this.e0, this.e1, this.e2, this.e3, this.lat2); + if (Math.abs(this.lat1 - this.lat2) >= ProjConstants.EPSLN) { + this.ns = (this.ms1 - this.ms2) / (this.ml2 - this.ml1); + } else { + this.ns = this.sinphi; + } + } else { + this.ns = this.sinphi; + } + this.g = this.ml1 + this.ms1/this.ns; + this.ml0 = ProjConstants.mlfn(this.e0, this.e1,this. e2, this.e3, this.lat0); + this.rh = this.a * (this.g - this.ml0); + } + + +/* Equidistant Conic forward equations--mapping lat,long to x,y + -----------------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number=p.x; + var lat:Number=p.y; + + /* Forward equations + -----------------*/ + var ml:Number = ProjConstants.mlfn(this.e0, this.e1, this.e2, this.e3, lat); + var rh1:Number = this.a * (this.g - ml); + var theta:Number = this.ns * ProjConstants.adjust_lon(lon - this.long0); + + var x:Number = this.x0 + rh1 * Math.sin(theta); + var y:Number = this.y0 + this.rh - rh1 * Math.cos(theta); + p.x=x; + p.y=y; + return p; + } + +/* Inverse equations + -----------------*/ + override public function inverse(p:ProjPoint):ProjPoint + { + p.x -= this.x0; + p.y = this.rh - p.y + this.y0; + var con:Number; + var rh1:Number; + if (this.ns >= 0) { + rh1 = Math.sqrt(p.x *p.x + p.y * p.y); + con = 1.0; + } else { + rh1 = -Math.sqrt(p.x *p. x +p. y * p.y); + con = -1.0; + } + var theta:Number = 0.0; + if (rh1 != 0.0) theta = Math.atan2(con *p.x, con *p.y); + var ml:Number = this.g - rh1 /this.a; + var lat:Number = this.phi3z(this.ml,this.e0,this.e1,this.e2,this.e3); + var lon:Number = ProjConstants.adjust_lon(this.long0 + theta / this.ns); + + p.x=lon; + p.y=lat; + return p; + } + +/* Function to compute latitude, phi3, for the inverse of the Equidistant + Conic projection. +-----------------------------------------------------------------*/ + private function phi3z(ml:Number,e0:Number,e1:Number,e2:Number,e3:Number):Number + { + var phi:Number; + var dphi:Number; + + phi = ml; + for (var i:int = 0; i < 15; i++) { + dphi = (ml + e1 * Math.sin(2.0 * phi) - e2 * Math.sin(4.0 * phi) + e3 * Math.sin(6.0 * phi))/ e0 - phi; + phi += dphi; + if (Math.abs(dphi) <= .0000000001) { + return phi; + } + } + trace("PHI3Z-CONV:Latitude failed to converge after 15 iterations"); + return 0; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjEqui.as b/com/gradoservice/proj4as/proj/ProjEqui.as new file mode 100755 index 00000000..f0bd98f9 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjEqui.as @@ -0,0 +1,90 @@ +/******************************************************************************* +NAME EQUIRECTANGULAR + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Equirectangular projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +PROGRAMMER DATE +---------- ---- +T. Mittan Mar, 1993 + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government + Printing Office, Washington D.C., 1989. +*******************************************************************************/ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.Datum; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.ProjPoint; + + public class ProjEqui extends AbstractProjProjection + { + public function ProjEqui(data:ProjParams) + { + super(data); + } + + + + override public function init():void + { + if(!this.x0) this.x0=0; + if(!this.y0) this.y0=0; + if(!this.lat0) this.lat0=0; + if(!this.long0) this.long0=0; + ///this.t2; + } + + + +/* Equirectangular forward equations--mapping lat,long to x,y + ---------------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + + var lon:Number=p.x; + var lat:Number=p.y; + + var dlon:Number = ProjConstants.adjust_lon(lon - this.long0); + var x:Number = this.x0 +this. a * dlon *Math.cos(this.lat0); + var y:Number = this.y0 + this.a * lat; + + this.t1=x; + this.t2=Math.cos(this.lat0); + p.x=x; + p.y=y; + return p; + } //equiFwd() + + + +/* Equirectangular inverse equations--mapping x,y to lat/long + ---------------------------------------------------------*/ + override public function inverse(p:ProjPoint):ProjPoint + { + + p.x -= this.x0; + p.y -= this.y0; + var lat:Number = p.y /this. a; + + if ( Math.abs(lat) > ProjConstants.HALF_PI) { + trace("equi:Inv:DataError"); + } + var lon:Number = ProjConstants.adjust_lon(this.long0 + p.x / (this.a * Math.cos(this.lat0))); + p.x=lon; + p.y=lat; + + return p; + }//equiInv() + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjGauss.as b/com/gradoservice/proj4as/proj/ProjGauss.as new file mode 100755 index 00000000..621912b2 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjGauss.as @@ -0,0 +1,62 @@ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjGauss extends AbstractProjProjection + { + + protected var phic0:Number; + protected var ratexp:Number; + + public function ProjGauss(data:ProjParams) + { + super(data); + } + + override public function init():void + { + var sphi:Number = Math.sin(this.lat0); + var cphi:Number = Math.cos(this.lat0); + cphi *= cphi; + this.rc = Math.sqrt(1.0 - this.es) / (1.0 - this.es * sphi * sphi); + this.c = Math.sqrt(1.0 + this.es * cphi * cphi / (1.0 - this.es)); + this.phic0 = Math.asin(sphi / this.c); + this.ratexp = 0.5 * this.c * this.e; + this.k = Math.tan(0.5 * this.phic0 + ProjConstants.FORTPI) / (Math.pow(Math.tan(0.5*this.lat0 + ProjConstants.FORTPI), this.c) * ProjConstants.srat(this.e*sphi, this.ratexp)); + } + + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number = p.x; + var lat:Number = p.y; + + p.y = 2.0 * Math.atan( this.k * Math.pow(Math.tan(0.5 * lat + ProjConstants.FORTPI), this.c) * ProjConstants.srat(this.e * Math.sin(lat), this.ratexp) ) - ProjConstants.HALF_PI; + p.x = this.c * lon; + return p; + } + + override public function inverse(p:ProjPoint):ProjPoint + { + var DEL_TOL:Number = 1e-14; + var lon:Number = p.x / this.c; + var lat:Number = p.y; + var num:Number = Math.pow(Math.tan(0.5 * lat + ProjConstants.FORTPI)/this.k, 1./this.c); + for (var i:int = ProjConstants.MAX_ITER; i>0; --i) { + lat = 2.0 * Math.atan(num * ProjConstants.srat(this.e * Math.sin(p.y), -0.5 * this.e)) - ProjConstants.HALF_PI; + if (Math.abs(lat - p.y) < DEL_TOL) break; + p.y = lat; + } + /* convergence failed */ + if (!i) { + trace("gauss:inverse:convergence failed"); + return null; + } + p.x = lon; + p.y = lat; + return p; + } + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjGstmerc.as b/com/gradoservice/proj4as/proj/ProjGstmerc.as new file mode 100755 index 00000000..c7325b56 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjGstmerc.as @@ -0,0 +1,74 @@ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjGstmerc extends AbstractProjProjection + { + + private var cp:Number; + private var lc:Number; + private var n2:Number; + private var rs:Number; + private var xs:Number; + private var ys:Number; + + public function ProjGstmerc(data:ProjParams) + { + super(data); + } + + override public function init():void + { + // array of: a, b, lon0, lat0, k0, x0, y0 + var temp:Number = this.b / this.a; + this.e = Math.sqrt(1.0 - temp*temp); + this.lc= this.long0; + this.rs= Math.sqrt(1.0+this.e*this.e*Math.pow(Math.cos(this.lat0),4.0)/(1.0-this.e*this.e)); + var sinz:Number= Math.sin(this.lat0); + var pc:Number= Math.asin(sinz/this.rs); + var sinzpc:Number= Math.sin(pc); + this.cp= ProjConstants.latiso(0.0,pc,sinzpc)-this.rs*ProjConstants.latiso(this.e,this.lat0,sinz); + this.n2= this.k0*this.a*Math.sqrt(1.0-this.e*this.e)/(1.0-this.e*this.e*sinz*sinz); + this.xs= this.x0; + this.ys= this.y0-this.n2*pc; + + if (!this.title) this.title = "Gauss Schreiber transverse mercator"; + } + + + // forward equations--mapping lat,long to x,y + // ----------------------------------------------------------------- + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number= p.x; + var lat:Number= p.y; + + var L:Number= this.rs*(lon-this.lc); + var Ls:Number= this.cp+(this.rs*ProjConstants.latiso(this.e,lat,Math.sin(lat))); + var lat1:Number= Math.asin(Math.sin(L)/ProjConstants.cosh(Ls)); + var Ls1:Number= ProjConstants.latiso(0.0,lat1,Math.sin(lat1)); + p.x= this.xs+(this.n2*Ls1); + p.y= this.ys+(this.n2*Math.atan(ProjConstants.sinh(Ls)/Math.cos(L))); + return p; + } + + // inverse equations--mapping x,y to lat/long + // ----------------------------------------------------------------- + override public function inverse(p:ProjPoint):ProjPoint + { + var x:Number= p.x; + var y:Number= p.y; + + var L:Number= Math.atan(ProjConstants.sinh((x-this.xs)/this.n2)/Math.cos((y-this.ys)/this.n2)); + var lat1:Number= Math.asin(Math.sin((y-this.ys)/this.n2)/ProjConstants.cosh((x-this.xs)/this.n2)); + var LC:Number= ProjConstants.latiso(0.0,lat1,Math.sin(lat1)); + p.x= this.lc+L/this.rs; + p.y= ProjConstants.invlatiso(this.e,(LC-this.cp)/this.rs); + return p; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjLaea.as b/com/gradoservice/proj4as/proj/ProjLaea.as new file mode 100755 index 00000000..9af7a6ba --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjLaea.as @@ -0,0 +1,127 @@ +/******************************************************************************* +NAME LAMBERT AZIMUTHAL EQUAL-AREA + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Lambert Azimuthal Equal-Area projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +PROGRAMMER DATE +---------- ---- +D. Steinwand, EROS March, 1991 + +This function was adapted from the Lambert Azimuthal Equal Area projection +code (FORTRAN) in the General Cartographic Transformation Package software +which is available from the U.S. Geological Survey National Mapping Division. + +ALGORITHM REFERENCES + +1. "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder, + The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355. + +2. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +3. "Software Documentation for GCTP General Cartographic Transformation + Package", U.S. Geological Survey National Mapping Division, May 1982. +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjLaea extends AbstractProjProjection + { + private var sin_lat_o:Number; + private var cos_lat_o:Number; + + public function ProjLaea(data:ProjParams) + { + super(data); + } + + /* Initialize the Lambert Azimuthal Equal Area projection + ------------------------------------------------------*/ + override public function init():void + { + this.sin_lat_o=Math.sin(this.lat0); + this.cos_lat_o=Math.cos(this.lat0); + } + + /* Lambert Azimuthal Equal Area forward equations--mapping lat,long to x,y + -----------------------------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + + /* Forward equations + -----------------*/ + var lon:Number=p.x; + var lat:Number=p.y; + var delta_lon:Number = ProjConstants.adjust_lon(lon - this.long0); + + //v 1.0 + var sin_lat:Number=Math.sin(lat); + var cos_lat:Number=Math.cos(lat); + + var sin_delta_lon:Number=Math.sin(delta_lon); + var cos_delta_lon:Number=Math.cos(delta_lon); + + var g:Number =this.sin_lat_o * sin_lat +this.cos_lat_o * cos_lat * cos_delta_lon; + if (g == -1.0) { + trace("laea:fwd:Point projects to a circle of radius "); + return null; + } + var ksp:Number = this.a * Math.sqrt(2.0 / (1.0 + g)); + var x:Number = ksp * cos_lat * sin_delta_lon + this.x0; + var y:Number = ksp * (this.cos_lat_o * sin_lat - this.sin_lat_o * cos_lat * cos_delta_lon) + this.y0; + p.x = x; + p.y = y + return p; + }//lamazFwd() + + /* Inverse equations + -----------------*/ + override public function inverse(p:ProjPoint):ProjPoint + { + p.x -= this.x0; + p.y -= this.y0; + + var Rh:Number = Math.sqrt(p.x *p.x +p.y * p.y); + var temp:Number = Rh / (2.0 * this.a); + + if (temp > 1) { + trace("laea:Inv:DataError"); + return null; + } + + var z:Number = 2.0 * ProjConstants.asinz(temp); + var sin_z:Number=Math.sin(z); + var cos_z:Number=Math.cos(z); + + var lon:Number =this.long0; + if (Math.abs(Rh) > ProjConstants.EPSLN) { + var lat:Number = ProjConstants.asinz(this.sin_lat_o * cos_z +this. cos_lat_o * sin_z *p.y / Rh); + temp =Math.abs(this.lat0) - ProjConstants.HALF_PI; + if (Math.abs(temp) > ProjConstants.EPSLN) { + temp = cos_z -this.sin_lat_o * Math.sin(lat); + if(temp!=0.0) lon=ProjConstants.adjust_lon(this.long0+Math.atan2(p.x*sin_z*this.cos_lat_o,temp*Rh)); + } else if (this.lat0 < 0.0) { + lon = ProjConstants.adjust_lon(this.long0 - Math.atan2(-p.x,p.y)); + } else { + lon = ProjConstants.adjust_lon(this.long0 + Math.atan2(p.x, -p.y)); + } + } else { + lat = this.lat0; + } + //return(OK); + p.x = lon; + p.y = lat; + return p; + }//lamazInv() + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjLcc.as b/com/gradoservice/proj4as/proj/ProjLcc.as new file mode 100755 index 00000000..6e043abd --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjLcc.as @@ -0,0 +1,161 @@ +/******************************************************************************* +NAME LAMBERT CONFORMAL CONIC + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Lambert Conformal Conic projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government +*******************************************************************************/ + + +//<2104> +proj=lcc +lat_1=10.16666666666667 +lat_0=10.16666666666667 +lon_0=-71.60561777777777 +k_0=1 +x0=-17044 +x0=-23139.97 +ellps=intl +units=m +no_defs no_defs + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjLcc extends AbstractProjProjection + { + private var f0:Number; + + public function ProjLcc(data:ProjParams) + { + super(data); + } + + override public function init():void + { + // array of: r_maj,r_min,lat1,lat2,c_lon,c_lat,false_east,false_north + //double c_lat; /* center latitude */ + //double c_lon; /* center longitude */ + //double lat1; /* first standard parallel */ + //double lat2; /* second standard parallel */ + //double r_maj; /* major axis */ + //double r_min; /* minor axis */ + //double false_east; /* x offset in meters */ + //double false_north; /* y offset in meters */ + + if (!this.lat2){this.lat2=this.lat0;}//if lat2 is not defined + if (!this.k0) this.k0 = 1.0; + + // Standard Parallels cannot be equal and on opposite sides of the equator + if (Math.abs(this.lat1+this.lat2) < ProjConstants.EPSLN) { + trace("lcc:init: Equal Latitudes"); + return; + } + + var temp:Number = this.b / this.a; + this.e = Math.sqrt(1.0 - temp*temp); + + var sin1:Number = Math.sin(this.lat1); + var cos1:Number = Math.cos(this.lat1); + var ms1:Number= ProjConstants.msfnz(this.e, sin1, cos1); + var ts1:Number = ProjConstants.tsfnz(this.e, this.lat1, sin1); + + var sin2:Number = Math.sin(this.lat2); + var cos2:Number = Math.cos(this.lat2); + var ms2:Number = ProjConstants.msfnz(this.e, sin2, cos2); + var ts2:Number = ProjConstants.tsfnz(this.e, this.lat2, sin2); + + var ts0:Number = ProjConstants.tsfnz(this.e, this.lat0, Math.sin(this.lat0)); + + if (Math.abs(this.lat1 - this.lat2) > ProjConstants.EPSLN) { + this.ns = Math.log(ms1/ms2)/Math.log(ts1/ts2); + } else { + this.ns = sin1; + } + this.f0 = ms1 / (this.ns * Math.pow(ts1, this.ns)); + this.rh = this.a * this.f0 * Math.pow(ts0, this.ns); + if (!this.title) this.title = "Lambert Conformal Conic"; + } + + + // Lambert Conformal conic forward equations--mapping lat,long to x,y + // ----------------------------------------------------------------- + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number = p.x; + var lat:Number = p.y; + + // convert to radians + if ( lat <= 90.0 && lat >= -90.0 && lon <= 180.0 && lon >= -180.0) { + //lon = lon * Proj4js.common.D2R; + //lat = lat * Proj4js.common.D2R; + } else { + trace("lcc:forward: llInputOutOfRange: "+ lon +" : " + lat); + return null; + } + + var con:Number = Math.abs( Math.abs(lat) - ProjConstants.HALF_PI); + var ts:Number; + var rh1:Number; + if (con > ProjConstants.EPSLN) { + ts = ProjConstants.tsfnz(this.e, lat, Math.sin(lat) ); + rh1 = this.a * this.f0 * Math.pow(ts, this.ns); + } else { + con = lat * this.ns; + if (con <= 0) { + trace("lcc:forward: No Projection"); + return null; + } + rh1 = 0; + } + var theta:Number = this.ns * ProjConstants.adjust_lon(lon - this.long0); + p.x = this.k0 * (rh1 * Math.sin(theta)) + this.x0; + p.y = this.k0 * (this.rh - rh1 * Math.cos(theta)) + this.y0; + + return p; + } + + // Lambert Conformal Conic inverse equations--mapping x,y to lat/long + // ----------------------------------------------------------------- + override public function inverse(p:ProjPoint):ProjPoint + { + var rh1:Number; + var con:Number; + var ts:Number; + var lat:Number; + var lon:Number; + var x:Number = (p.x - this.x0)/this.k0; + var y:Number = (this.rh - (p.y - this.y0)/this.k0); + if (this.ns > 0) { + rh1 = Math.sqrt (x * x + y * y); + con = 1.0; + } else { + rh1 = -Math.sqrt (x * x + y * y); + con = -1.0; + } + var theta:Number = 0.0; + if (rh1 != 0) { + theta = Math.atan2((con * x),(con * y)); + } + if ((rh1 != 0) || (this.ns > 0.0)) { + con = 1.0/this.ns; + ts = Math.pow((rh1/(this.a * this.f0)), con); + lat = ProjConstants.phi2z(this.e, ts); + if (lat == -9999) return null; + } else { + lat = -ProjConstants.HALF_PI; + } + lon = ProjConstants.adjust_lon(theta/this.ns + this.long0); + + p.x = lon; + p.y = lat; + return p; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjLonglat.as b/com/gradoservice/proj4as/proj/ProjLonglat.as new file mode 100755 index 00000000..e9075931 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjLonglat.as @@ -0,0 +1,13 @@ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.Datum; + + public class ProjLonglat extends AbstractProjProjection + { + public function ProjLonglat(data:ProjParams) + { + super(data); + } + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjMerc.as b/com/gradoservice/proj4as/proj/ProjMerc.as new file mode 100755 index 00000000..88543599 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjMerc.as @@ -0,0 +1,120 @@ +/******************************************************************************* +NAME MERCATOR + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Mercator projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +PROGRAMMER DATE +---------- ---- +D. Steinwand, EROS Nov, 1991 +T. Mittan Mar, 1993 + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government + Printing Office, Washington D.C., 1989. +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjMerc extends AbstractProjProjection + { + public function ProjMerc(data:ProjParams) + { + super(data); + } + + override public function init():void + { + //?this.temp = this.r_minor / this.r_major; + //this.temp = this.b / this.a; + //this.es = 1.0 - Math.sqrt(this.temp); + //this.e = Math.sqrt( this.es ); + //?this.m1 = Math.cos(this.lat_origin) / (Math.sqrt( 1.0 - this.es * Math.sin(this.lat_origin) * Math.sin(this.lat_origin))); + //this.m1 = Math.cos(0.0) / (Math.sqrt( 1.0 - this.es * Math.sin(0.0) * Math.sin(0.0))); + if (this.lat_ts) { + if (this.sphere) { + this.k0 = Math.cos(this.lat_ts); + } else { + this.k0 = ProjConstants.msfnz(this.es, Math.sin(this.lat_ts), Math.cos(this.lat_ts)); + } + } + } + + /* Mercator forward equations--mapping lat,long to x,y + --------------------------------------------------*/ + + override public function forward(p:ProjPoint):ProjPoint + { + //alert("ll2m coords : "+coords); + var lon:Number = p.x; + var lat:Number = p.y; + // convert to radians + if ( lat*ProjConstants.R2D > 90.0 && + lat*ProjConstants.R2D < -90.0 && + lon*ProjConstants.R2D > 180.0 && + lon*ProjConstants.R2D < -180.0) { + trace("merc:forward: llInputOutOfRange: "+ lon +" : " + lat); + return null; + } + + var x:Number,y:Number; + if(Math.abs( Math.abs(lat) - ProjConstants.HALF_PI) <= ProjConstants.EPSLN) { + trace("merc:forward: ll2mAtPoles"); + return null; + } else { + if (this.sphere) { + x = this.x0 + this.a * this.k0 * ProjConstants.adjust_lon(lon - this.long0); + y = this.y0 + this.a * this.k0 * Math.log(Math.tan(ProjConstants.FORTPI + 0.5*lat)); + } else { + var sinphi:Number = Math.sin(lat); + var ts:Number = ProjConstants.tsfnz(this.e,lat,sinphi); + x = this.x0 + this.a * this.k0 * ProjConstants.adjust_lon(lon - this.long0); + y = this.y0 - this.a * this.k0 * Math.log(ts); + } + p.x = x; + p.y = y; + return p; + } + } + + + /* Mercator inverse equations--mapping x,y to lat/long + --------------------------------------------------*/ + override public function inverse(p:ProjPoint):ProjPoint + { + var x:Number = p.x - this.x0; + var y:Number = p.y - this.y0; + var lon:Number,lat:Number; + + if (this.sphere) { + lat = ProjConstants.HALF_PI - 2.0 * Math.atan(Math.exp(-y / this.a * this.k0)); + } else { + var ts:Number = Math.exp(-y / (this.a * this.k0)); + lat = ProjConstants.phi2z(this.e,ts); + if(lat == -9999) { + trace("merc:inverse: lat = -9999"); + return null; + } + } + lon = ProjConstants.adjust_lon(this.long0+ x / (this.a * this.k0)); + + p.x = lon; + p.y = lat; + return p; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjMill.as b/com/gradoservice/proj4as/proj/ProjMill.as new file mode 100755 index 00000000..36ebb4d1 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjMill.as @@ -0,0 +1,83 @@ +/******************************************************************************* +NAME MILLER CYLINDRICAL + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Miller Cylindrical projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +PROGRAMMER DATE +---------- ---- +T. Mittan March, 1993 + +This function was adapted from the Lambert Azimuthal Equal Area projection +code (FORTRAN) in the General Cartographic Transformation Package software +which is available from the U.S. Geological Survey National Mapping Division. + +ALGORITHM REFERENCES + +1. "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder, + The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355. + +2. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +3. "Software Documentation for GCTP General Cartographic Transformation + Package", U.S. Geological Survey National Mapping Division, May 1982. +*******************************************************************************/ + + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjMill extends AbstractProjProjection + { + public function ProjMill(data:ProjParams) + { + super(data); + } + + override public function init():void + { + //no-op + } + + + /* Miller Cylindrical forward equations--mapping lat,long to x,y + ------------------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number=p.x; + var lat:Number=p.y; + /* Forward equations + -----------------*/ + var dlon:Number = ProjConstants.adjust_lon(lon -this.long0); + var x:Number = this.x0 + this.a * dlon; + var y:Number = this.y0 + this.a * Math.log(Math.tan((ProjConstants.PI / 4.0) + (lat / 2.5))) * 1.25; + + p.x=x; + p.y=y; + return p; + }//millFwd() + + /* Miller Cylindrical inverse equations--mapping x,y to lat/long + ------------------------------------------------------------*/ + override public function inverse(p:ProjPoint):ProjPoint + { + p.x -= this.x0; + p.y -= this.y0; + + var lon:Number = ProjConstants.adjust_lon(this.long0 + p.x /this.a); + var lat:Number = 2.5 * (Math.atan(Math.exp(0.8*p.y/this.a)) - ProjConstants.PI / 4.0); + + p.x=lon; + p.y=lat; + return p; + }//millInv() + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjMoll.as b/com/gradoservice/proj4as/proj/ProjMoll.as new file mode 100755 index 00000000..5e29222f --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjMoll.as @@ -0,0 +1,113 @@ +/******************************************************************************* +NAME MOLLWEIDE + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the MOllweide projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +PROGRAMMER DATE +---------- ---- +D. Steinwand, EROS May, 1991; Updated Sept, 1992; Updated Feb, 1993 +S. Nelson, EDC Jun, 2993; Made corrections in precision and + number of iterations. + +ALGORITHM REFERENCES + +1. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government + Printing Office, Washington D.C., 1989. + +2. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. +*******************************************************************************/ + + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjMoll extends AbstractProjProjection + { + public function ProjMoll(data:ProjParams) + { + super(data); + } + + override public function init():void + { + //no-op + } + + /* Mollweide forward equations--mapping lat,long to x,y + ----------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + /* Forward equations + -----------------*/ + var lon:Number=p.x; + var lat:Number=p.y; + + var delta_lon:Number = ProjConstants.adjust_lon(lon - this.long0); + var theta:Number = lat; + var con:Number = ProjConstants.PI * Math.sin(lat); + + /* Iterate using the Newton-Raphson method to find theta + -----------------------------------------------------*/ + for (var i:int=0;;i++) { + var delta_theta:Number = -(theta + Math.sin(theta) - con)/ (1.0 + Math.cos(theta)); + theta += delta_theta; + if (Math.abs(delta_theta) < ProjConstants.EPSLN) break; + if (i >= 50) { + trace("moll:Fwd:IterationError"); + //return(241); + } + } + theta /= 2.0; + + /* If the latitude is 90 deg, force the x coordinate to be "0 + false easting" + this is done here because of precision problems with "cos(theta)" + --------------------------------------------------------------------------*/ + if (ProjConstants.PI/2 - Math.abs(lat) < ProjConstants.EPSLN) delta_lon =0; + var x:Number = 0.900316316158 * this.a * delta_lon * Math.cos(theta) + this.x0; + var y:Number = 1.4142135623731 * this.a * Math.sin(theta) + this.y0; + + p.x=x; + p.y=y; + return p; + } + + override public function inverse(p:ProjPoint):ProjPoint + { + var theta:Number; + var arg:Number; + + /* Inverse equations + -----------------*/ + p.x-= this.x0; + //~ p.y -= this.y0; + arg = p.y / (1.4142135623731 * this.a); + + /* Because of division by zero problems, 'arg' can not be 1.0. Therefore + a number very close to one is used instead. + -------------------------------------------------------------------*/ + if(Math.abs(arg) > 0.999999999999) arg=0.999999999999; + theta =Math.asin(arg); + var lon:Number = ProjConstants.adjust_lon(this.long0 + (p.x / (0.900316316158 * this.a * Math.cos(theta)))); + if(lon < (-ProjConstants.PI)) lon= -ProjConstants.PI; + if(lon > ProjConstants.PI) lon= ProjConstants.PI; + arg = (2.0 * theta + Math.sin(2.0 * theta)) / ProjConstants.PI; + if(Math.abs(arg) > 1.0)arg=1.0; + var lat:Number = Math.asin(arg); + //return(OK); + + p.x=lon; + p.y=lat; + return p; + } + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjNzmg.as b/com/gradoservice/proj4as/proj/ProjNzmg.as new file mode 100755 index 00000000..d34eeba2 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjNzmg.as @@ -0,0 +1,292 @@ +/******************************************************************************* +NAME NEW ZEALAND MAP GRID + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the New Zealand Map Grid projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + + +ALGORITHM REFERENCES + +1. Department of Land and Survey Technical Circular 1973/32 + http://www.linz.govt.nz/docs/miscellaneous/nz-map-definition.pdf + +2. OSG Technical Report 4.1 + http://www.linz.govt.nz/docs/miscellaneous/nzmg.pdf + + +IMPLEMENTATION NOTES + +The two references use different symbols for the calculated values. This +implementation uses the variable names similar to the symbols in reference [1]. + +The alogrithm uses different units for delta latitude and delta longitude. +The delta latitude is assumed to be in units of seconds of arc x 10^-5. +The delta longitude is the usual radians. Look out for these conversions. + +The algorithm is described using complex arithmetic. There were three +options: + * find and use a Javascript library for complex arithmetic + * write my own complex library + * expand the complex arithmetic by hand to simple arithmetic + +This implementation has expanded the complex multiplication operations +into parallel simple arithmetic operations for the real and imaginary parts. +The imaginary part is way over to the right of the display; this probably +violates every coding standard in the world, but, to me, it makes it much +more obvious what is going on. + +The following complex operations are used: + - addition + - multiplication + - division + - complex number raised to integer power + - summation + +A summary of complex arithmetic operations: + (from http://en.wikipedia.org/wiki/Complex_arithmetic) + addition: (a + bi) + (c + di) = (a + c) + (b + d)i + subtraction: (a + bi) - (c + di) = (a - c) + (b - d)i + multiplication: (a + bi) x (c + di) = (ac - bd) + (bc + ad)i + division: (a + bi) / (c + di) = [(ac + bd)/(cc + dd)] + [(bc - ad)/(cc + dd)]i + +The algorithm needs to calculate summations of simple and complex numbers. This is +implemented using a for-loop, pre-loading the summed value to zero. + +The algorithm needs to calculate theta^2, theta^3, etc while doing a summation. +There are three possible implementations: + - use Math.pow in the summation loop - except for complex numbers + - precalculate the values before running the loop + - calculate theta^n = theta^(n-1) * theta during the loop +This implementation uses the third option for both real and complex arithmetic. + +For example + psi_n = 1; + sum = 0; + for (n = 1; n <=6; n++) { + psi_n1 = psi_n * psi; // calculate psi^(n+1) + psi_n = psi_n1; + sum = sum + A[n] * psi_n; + } + + +TEST VECTORS + +NZMG E, N: 2487100.638 6751049.719 metres +NZGD49 long, lat: 172.739194 -34.444066 degrees + +NZMG E, N: 2486533.395 6077263.661 metres +NZGD49 long, lat: 172.723106 -40.512409 degrees + +NZMG E, N: 2216746.425 5388508.765 metres +NZGD49 long, lat: 169.172062 -46.651295 degrees + +Note that these test vectors convert from NZMG metres to lat/long referenced +to NZGD49, not the more usual WGS84. The difference is about 70m N/S and about +10m E/W. + +These test vectors are provided in reference [1]. Many more test +vectors are available in + http://www.linz.govt.nz/docs/topography/topographicdata/placenamesdatabase/nznamesmar08.zip +which is a catalog of names on the 260-series maps. + + +EPSG CODES + +NZMG EPSG:27200 +NZGD49 EPSG:4272 + +http://spatialreference.org/ defines these as + Proj4js.defs["EPSG:4272"] = "+proj=longlat +ellps=intl +datum=nzgd49 +no_defs "; + Proj4js.defs["EPSG:27200"] = "+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=intl +datum=nzgd49 +units=m +no_defs "; + + +LICENSE + Copyright: Stephen Irons 2008 + Released under terms of the LGPL as per: http://www.gnu.org/copyleft/lesser.html + +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjNzmg extends AbstractProjProjection + { + private var A:Array = []; + private var B_re:Array = []; + private var B_im:Array = []; + private var C_re:Array = []; + private var C_im:Array = []; + private var D:Array = []; + private var iterations:int = 1; + + public function ProjNzmg(data:ProjParams) + { + super(data); + } + + override public function init():void + { + this.A[1] = +0.6399175073; + this.A[2] = -0.1358797613; + this.A[3] = +0.063294409; + this.A[4] = -0.02526853; + this.A[5] = +0.0117879; + this.A[6] = -0.0055161; + this.A[7] = +0.0026906; + this.A[8] = -0.001333; + this.A[9] = +0.00067; + this.A[10] = -0.00034; + + this.B_re[1] = +0.7557853228; this.B_im[1] = 0.0; + this.B_re[2] = +0.249204646; this.B_im[2] = +0.003371507; + this.B_re[3] = -0.001541739; this.B_im[3] = +0.041058560; + this.B_re[4] = -0.10162907; this.B_im[4] = +0.01727609; + this.B_re[5] = -0.26623489; this.B_im[5] = -0.36249218; + this.B_re[6] = -0.6870983; this.B_im[6] = -1.1651967; + + this.C_re[1] = +1.3231270439; this.C_im[1] = 0.0; + this.C_re[2] = -0.577245789; this.C_im[2] = -0.007809598; + this.C_re[3] = +0.508307513; this.C_im[3] = -0.112208952; + this.C_re[4] = -0.15094762; this.C_im[4] = +0.18200602; + this.C_re[5] = +1.01418179; this.C_im[5] = +1.64497696; + this.C_re[6] = +1.9660549; this.C_im[6] = +2.5127645; + + this.D[1] = +1.5627014243; + this.D[2] = +0.5185406398; + this.D[3] = -0.03333098; + this.D[4] = -0.1052906; + this.D[5] = -0.0368594; + this.D[6] = +0.007317; + this.D[7] = +0.01220; + this.D[8] = +0.00394; + this.D[9] = -0.0013; + } + + /** + New Zealand Map Grid Forward - long/lat to x/y + long/lat in radians + */ + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number = p.x; + var lat:Number = p.y; + + var delta_lat:Number = lat - this.lat0; + var delta_lon:Number = lon - this.long0; + + // 1. Calculate d_phi and d_psi ... // and d_lambda + // For this algorithm, delta_latitude is in seconds of arc x 10-5, so we need to scale to those units. Longitude is radians. + var d_phi:Number = delta_lat / ProjConstants.SEC_TO_RAD * 1E-5; var d_lambda:Number = delta_lon; + var d_phi_n:Number = 1; // d_phi^0 + + var d_psi:Number = 0; + for (var n:int = 1; n <= 10; n++) { + d_phi_n = d_phi_n * d_phi; + d_psi = d_psi + this.A[n] * d_phi_n; + } + + // 2. Calculate theta + var th_re:Number = d_psi; var th_im:Number = d_lambda; + + // 3. Calculate z + var th_n_re:Number = 1; var th_n_im:Number = 0; // theta^0 + var th_n_re1:Number; var th_n_im1:Number; + + var z_re:Number = 0; var z_im:Number = 0; + for (n = 1; n <= 6; n++) { + th_n_re1 = th_n_re*th_re - th_n_im*th_im; th_n_im1 = th_n_im*th_re + th_n_re*th_im; + th_n_re = th_n_re1; th_n_im = th_n_im1; + z_re = z_re + this.B_re[n]*th_n_re - this.B_im[n]*th_n_im; z_im = z_im + this.B_im[n]*th_n_re + this.B_re[n]*th_n_im; + } + + // 4. Calculate easting and northing + var x:Number = (z_im * this.a) + this.x0; + var y:Number = (z_re * this.a) + this.y0; + + p.x = x; p.y = y; + + return p; + } + + + /** + New Zealand Map Grid Inverse - x/y to long/lat + */ + override public function inverse(p:ProjPoint):ProjPoint + { + var x:Number = p.x; + var y:Number = p.y; + + var delta_x:Number = x - this.x0; + var delta_y:Number = y - this.y0; + + // 1. Calculate z + var z_re:Number = delta_y / this.a; var z_im:Number = delta_x / this.a; + + // 2a. Calculate theta - first approximation gives km accuracy + var z_n_re:Number = 1; var z_n_im:Number = 0; // z^0 + var z_n_re1:Number; var z_n_im1:Number; + + var th_re:Number = 0; var th_im:Number = 0; + for (var n:int = 1; n <= 6; n++) { + z_n_re1 = z_n_re*z_re - z_n_im*z_im; z_n_im1 = z_n_im*z_re + z_n_re*z_im; + z_n_re = z_n_re1; z_n_im = z_n_im1; + th_re = th_re + this.C_re[n]*z_n_re - this.C_im[n]*z_n_im; th_im = th_im + this.C_im[n]*z_n_re + this.C_re[n]*z_n_im; + } + + // 2b. Iterate to refine the accuracy of the calculation + // 0 iterations gives km accuracy + // 1 iteration gives m accuracy -- good enough for most mapping applications + // 2 iterations bives mm accuracy + for (var i:int = 0; i < this.iterations; i++) { + var th_n_re:Number = th_re; var th_n_im:Number = th_im; + var th_n_re1:Number; var th_n_im1:Number; + + var num_re:Number = z_re; var num_im:Number = z_im; + for (n = 2; n <= 6; n++) { + th_n_re1 = th_n_re*th_re - th_n_im*th_im; th_n_im1 = th_n_im*th_re + th_n_re*th_im; + th_n_re = th_n_re1; th_n_im = th_n_im1; + num_re = num_re + (n-1)*(this.B_re[n]*th_n_re - this.B_im[n]*th_n_im); num_im = num_im + (n-1)*(this.B_im[n]*th_n_re + this.B_re[n]*th_n_im); + } + + th_n_re = 1; th_n_im = 0; + var den_re:Number = this.B_re[1]; var den_im:Number = this.B_im[1]; + for (n = 2; n <= 6; n++) { + th_n_re1 = th_n_re*th_re - th_n_im*th_im; th_n_im1 = th_n_im*th_re + th_n_re*th_im; + th_n_re = th_n_re1; th_n_im = th_n_im1; + den_re = den_re + n * (this.B_re[n]*th_n_re - this.B_im[n]*th_n_im); den_im = den_im + n * (this.B_im[n]*th_n_re + this.B_re[n]*th_n_im); + } + + // Complex division + var den2:Number = den_re*den_re + den_im*den_im; + th_re = (num_re*den_re + num_im*den_im) / den2; th_im = (num_im*den_re - num_re*den_im) / den2; + } + + // 3. Calculate d_phi ... // and d_lambda + var d_psi:Number = th_re; var d_lambda:Number = th_im; + var d_psi_n:Number = 1; // d_psi^0 + + var d_phi:Number = 0; + for (n = 1; n <= 9; n++) { + d_psi_n = d_psi_n * d_psi; + d_phi = d_phi + this.D[n] * d_psi_n; + } + + // 4. Calculate latitude and longitude + // d_phi is calcuated in second of arc * 10^-5, so we need to scale back to radians. d_lambda is in radians. + var lat:Number = this.lat0 + (d_phi * ProjConstants.SEC_TO_RAD * 1E5); + var lon:Number = this.long0 + d_lambda; + + p.x = lon; + p.y = lat; + + return p; + } + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjOmerc.as b/com/gradoservice/proj4as/proj/ProjOmerc.as new file mode 100755 index 00000000..1a262f5b --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjOmerc.as @@ -0,0 +1,299 @@ +/******************************************************************************* +NAME OBLIQUE MERCATOR (HOTINE) + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Oblique Mercator projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +PROGRAMMER DATE +---------- ---- +T. Mittan Mar, 1993 + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government + Printing Office, Washington D.C., 1989. +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjOmerc extends AbstractProjProjection + { + private var d:Number; + private var f:Number; + private var h:Number; + private var j:Number; + private var l:Number; + private var p:Number; + private var u:Number; + private var dlon:Number; + private var al:Number; + private var bl:Number; + private var el:Number; + private var com:Number; + private var cos_p20:Number; + private var cosaz:Number; + private var cosgam:Number; + private var at1:Number; + private var gam:Number; + private var gama:Number; + private var lon1:Number; + private var lon2:Number; + private var sin_p20:Number; + private var sinaz:Number; + private var singam:Number; + private var ts:Number; + private var ts1:Number; + private var ts2:Number; + + public function ProjOmerc(data:ProjParams) + { + super(data); + } + + + override public function init():void + { + if (!this.mode) this.mode=0; + if (!this.lon1) {this.lon1=0;this.mode=1;} + if (!this.lon2) this.lon2=0; + if (!this.lat2) this.lat2=0; + + /* Place parameters in static storage for common use + -------------------------------------------------*/ + var temp:Number = this.b/ this.a; + var es:Number = 1.0 - Math.pow(temp,2); + var e:Number = Math.sqrt(es); + + this.sin_p20=Math.sin(this.lat0); + this.cos_p20=Math.cos(this.lat0); + + this.con = 1.0 - this.es * this.sin_p20 * this.sin_p20; + this.com = Math.sqrt(1.0 - es); + this.bl = Math.sqrt(1.0 + this.es * Math.pow(this.cos_p20,4.0)/(1.0 - es)); + this.al = this.a * this.bl * this.k0 * this.com / this.con; + if (Math.abs(this.lat0) < ProjConstants.EPSLN) { + this.ts = 1.0; + this.d = 1.0; + this.el = 1.0; + } else { + this.ts = ProjConstants.tsfnz(this.e,this.lat0,this.sin_p20); + this.con = Math.sqrt(this.con); + this.d = this.bl * this.com / (this.cos_p20 * this.con); + if ((this.d * this.d - 1.0) > 0.0) { + if (this.lat0 >= 0.0) { + this.f = this.d + Math.sqrt(this.d * this.d - 1.0); + } else { + this.f = this.d - Math.sqrt(this.d * this.d - 1.0); + } + } else { + this.f = this.d; + } + this.el = this.f * Math.pow(this.ts,this.bl); + } + + //this.longc=52.60353916666667; + + if (this.mode != 0) { + this.g = .5 * (this.f - 1.0/this.f); + this.gama = ProjConstants.asinz(Math.sin(this.alpha) / this.d); + this.longc= this.longc - ProjConstants.asinz(this.g * Math.tan(this.gama))/this.bl; + + /* Report parameters common to format B + -------------------------------------*/ + //genrpt(azimuth * R2D,"Azimuth of Central Line: "); + //cenlon(lon_origin); + // cenlat(lat_origin); + + this.con = Math.abs(this.lat0); + if ((this.con > ProjConstants.EPSLN) && (Math.abs(this.con - ProjConstants.HALF_PI) > ProjConstants.EPSLN)) { + this.singam=Math.sin(this.gama); + this.cosgam=Math.cos(this.gama); + + this.sinaz=Math.sin(this.alpha); + this.cosaz=Math.cos(this.alpha); + + if (this.lat0>= 0) { + this.u = (this.al / this.bl) * Math.atan(Math.sqrt(this.d*this.d - 1.0)/this.cosaz); + } else { + this.u = -(this.al / this.bl) *Math.atan(Math.sqrt(this.d*this.d - 1.0)/this.cosaz); + } + } else { + trace("omerc:Init:DataError"); + } + } else { + this.sinphi =Math. sin(this.at1); + this.ts1 = ProjConstants.tsfnz(this.e,this.lat1,this.sinphi); + this.sinphi = Math.sin(this.lat2); + this.ts2 = ProjConstants.tsfnz(this.e,this.lat2,this.sinphi); + this.h = Math.pow(this.ts1,this.bl); + this.l = Math.pow(this.ts2,this.bl); + this.f = this.el/this.h; + this.g = .5 * (this.f - 1.0/this.f); + this.j = (this.el * this.el - this.l * this.h)/(this.el * this.el + this.l * this.h); + this.p = (this.l - this.h) / (this.l + this.h); + this.dlon = this.lon1 - this.lon2; + if (this.dlon < -ProjConstants.PI) this.lon2 = this.lon2 - 2.0 * ProjConstants.PI; + if (this.dlon > ProjConstants.PI) this.lon2 = this.lon2 + 2.0 * ProjConstants.PI; + this.dlon = this.lon1 - this.lon2; + this.longc = .5 * (this.lon1 + this.lon2) -Math.atan(this.j * Math.tan(.5 * this.bl * this.dlon)/this.p)/this.bl; + this.dlon = ProjConstants.adjust_lon(this.lon1 - this.longc); + this.gama = Math.atan(Math.sin(this.bl * this.dlon)/this.g); + this.alpha = ProjConstants.asinz(this.d * Math.sin(this.gama)); + + /* Report parameters common to format A + -------------------------------------*/ + + if (Math.abs(this.lat1 - this.lat2) <= ProjConstants.EPSLN) { + trace("omercInitDataError"); + //return(202); + } else { + this.con = Math.abs(this.lat1); + } + if ((this.con <= ProjConstants.EPSLN) || (Math.abs(this.con - ProjConstants.HALF_PI) <= ProjConstants.EPSLN)) { + trace("omercInitDataError"); + //return(202); + } else { + if (Math.abs(Math.abs(this.lat0) - ProjConstants.HALF_PI) <= ProjConstants.EPSLN) { + trace("omercInitDataError"); + //return(202); + } + } + + this.singam=Math.sin(this.gam); + this.cosgam=Math.cos(this.gam); + + this.sinaz=Math.sin(this.alpha); + this.cosaz=Math.cos(this.alpha); + + + if (this.lat0 >= 0) { + this.u = (this.al/this.bl) * Math.atan(Math.sqrt(this.d * this.d - 1.0)/this.cosaz); + } else { + this.u = -(this.al/this.bl) * Math.atan(Math.sqrt(this.d * this.d - 1.0)/this.cosaz); + } + } + } + + + /* Oblique Mercator forward equations--mapping lat,long to x,y + ----------------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + var theta:Number; /* angle */ + var sin_phi:Number, cos_phi:Number;/* sin and cos value */ + var b:Number; /* temporary values */ + var c:Number, t:Number, tq:Number; /* temporary values */ + var con:Number, n:Number, ml:Number; /* cone constant, small m */ + var q:Number,us:Number,vl:Number; + var ul:Number,vs:Number; + var s:Number; + var dlon:Number; + var ts1:Number; + + var lon:Number=p.x; + var lat:Number=p.y; + /* Forward equations + -----------------*/ + sin_phi = Math.sin(lat); + dlon = ProjConstants.adjust_lon(lon - this.longc); + vl = Math.sin(this.bl * dlon); + if (Math.abs(Math.abs(lat) - ProjConstants.HALF_PI) > ProjConstants.EPSLN) { + ts1 = ProjConstants.tsfnz(this.e,lat,sin_phi); + q = this.el / (Math.pow(ts1,this.bl)); + s = .5 * (q - 1.0 / q); + t = .5 * (q + 1.0/ q); + ul = (s * this.singam - vl * this.cosgam) / t; + con = Math.cos(this.bl * dlon); + if (Math.abs(con) < .0000001) { + us = this.al * this.bl * dlon; + } else { + us = this.al * Math.atan((s * this.cosgam + vl * this.singam) / con)/this.bl; + if (con < 0) us = us + ProjConstants.PI * this.al / this.bl; + } + } else { + if (lat >= 0) { + ul = this.singam; + } else { + ul = -this.singam; + } + us = this.al * lat / this.bl; + } + if (Math.abs(Math.abs(ul) - 1.0) <= ProjConstants.EPSLN) { + //alert("Point projects into infinity","omer-for"); + trace("omercFwdInfinity"); + //return(205); + } + vs = .5 * this.al * Math.log((1.0 - ul)/(1.0 + ul)) / this.bl; + us = us - this.u; + var x:Number = this.x0 + vs * this.cosaz + us * this.sinaz; + var y:Number = this.y0 + us * this.cosaz - vs * this.sinaz; + + p.x=x; + p.y=y; + return p; + } + + override public function inverse(p:ProjPoint):ProjPoint + { + var delta_lon:Number; /* Delta longitude (Given longitude - center */ + var theta:Number; /* angle */ + var delta_theta:Number; /* adjusted longitude */ + var sin_phi:Number, cos_phi:Number;/* sin and cos value */ + var b:Number; /* temporary values */ + var c:Number, t:Number, tq:Number; /* temporary values */ + var con:Number, n:Number, ml:Number; /* cone constant, small m */ + var vs:Number,us:Number,q:Number,s:Number,ts1:Number; + var vl:Number,ul:Number,bs:Number; + var dlon:Number; + var flag:Number; + + /* Inverse equations + -----------------*/ + p.x -= this.x0; + p.y -= this.y0; + flag = 0; + vs = p.x * this.cosaz - p.y * this.sinaz; + us = p.y * this.cosaz + p.x * this.sinaz; + us = us + this.u; + q = Math.exp(-this.bl * vs / this.al); + s = .5 * (q - 1.0/q); + t = .5 * (q + 1.0/q); + vl = Math.sin(this.bl * us / this.al); + ul = (vl * this.cosgam + s * this.singam)/t; + if (Math.abs(Math.abs(ul) - 1.0) <= ProjConstants.EPSLN) + { + var lon:Number = this.longc; + if (ul >= 0.0) { + var lat:Number = ProjConstants.HALF_PI; + } else { + lat = -ProjConstants.HALF_PI; + } + } else { + con = 1.0 / this.bl; + ts1 =Math.pow((this.el / Math.sqrt((1.0 + ul) / (1.0 - ul))),con); + lat = ProjConstants.phi2z(this.e,ts1); + //if (flag != 0) + //return(flag); + //~ con = Math.cos(this.bl * us /al); + theta = this.longc - Math.atan2((s * this.cosgam - vl * this.singam) , con)/this.bl; + lon = ProjConstants.adjust_lon(theta); + } + p.x=lon; + p.y=lat; + return p; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjOrtho.as b/com/gradoservice/proj4as/proj/ProjOrtho.as new file mode 100755 index 00000000..2f38a6cf --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjOrtho.as @@ -0,0 +1,131 @@ +/******************************************************************************* +NAME ORTHOGRAPHIC + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Orthographic projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +PROGRAMMER DATE +---------- ---- +T. Mittan Mar, 1993 + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government + Printing Office, Washington D.C., 1989. +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjOrtho extends AbstractProjProjection + { + + private var cos_p14:Number; + private var sin_p14:Number; + + public function ProjOrtho(data:ProjParams) + { + super(data); + } + + override public function init():void + { + //double temp; /* temporary variable */ + + /* Place parameters in static storage for common use + -------------------------------------------------*/; + this.sin_p14=Math.sin(this.lat0); + this.cos_p14=Math.cos(this.lat0); + } + + + /* Orthographic forward equations--mapping lat,long to x,y + ---------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + var sinphi:Number, cosphi:Number; /* sin and cos value */ + var dlon:Number; /* delta longitude value */ + var coslon:Number; /* cos of longitude */ + var ksp:Number; /* scale factor */ + var g:Number; + var lon:Number=p.x; + var lat:Number=p.y; + var x:Number,y:Number; + /* Forward equations + -----------------*/ + dlon = ProjConstants.adjust_lon(lon - this.long0); + + sinphi=Math.sin(lat); + cosphi=Math.cos(lat); + + coslon = Math.cos(dlon); + g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon; + ksp = 1.0; + if ((g > 0) || (Math.abs(g) <= ProjConstants.EPSLN)) { + x = this.a * ksp * cosphi * Math.sin(dlon); + y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon); + } else { + trace("orthoFwdPointError"); + } + p.x=x; + p.y=y; + return p; + } + + + override public function inverse(p:ProjPoint):ProjPoint + { + var rh:Number; /* height above ellipsoid */ + var x:Number,y:Number,z:Number; /* angle */ + var sinz:Number,cosz:Number,cosi:Number; /* sin of z and cos of z */ + var temp:Number; + var con:Number; + var lon:Number , lat:Number; + /* Inverse equations + -----------------*/ + p.x -= this.x0; + p.y -= this.y0; + rh = Math.sqrt(p.x * p.x + p.y * p.y); + if (rh > this.a + .0000001) { + trace("orthoInvDataError"); + } + z = ProjConstants.asinz(rh / this.a); + + sinz=Math.sin(z); + cosi=Math.cos(z); + + lon = this.long0; + if (Math.abs(rh) <= ProjConstants.EPSLN) { + lat = this.lat0; + } + lat = ProjConstants.asinz(cosz * this.sin_p14 + (y * sinz * this.cos_p14)/rh); + con = Math.abs(lat0) - ProjConstants.HALF_PI; + if (Math.abs(con) <= ProjConstants.EPSLN) { + if (this.lat0 >= 0) { + lon = ProjConstants.adjust_lon(this.long0 + Math.atan2(p.x, -p.y)); + } else { + lon = ProjConstants.adjust_lon(this.long0 -Math.atan2(-p.x, p.y)); + } + } + con = cosz - this.sin_p14 * Math.sin(lat); + if ((Math.abs(con) >= ProjConstants.EPSLN) || (Math.abs(x) >= ProjConstants.EPSLN)) { + lon = ProjConstants.adjust_lon(this.long0 + Math.atan2((p.x * sinz * this.cos_p14), (con * rh))); + } + p.x=lon; + p.y=lat; + return p; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjParams.as b/com/gradoservice/proj4as/proj/ProjParams.as new file mode 100755 index 00000000..782f5a85 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjParams.as @@ -0,0 +1,71 @@ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.Datum; + + public class ProjParams + { + + public var title:Object; + + /** + * Property: projName + * The projection class for this projection, e.g. lcc (lambert conformal conic, + * or merc for mercator. These are exactly equicvalent to their Proj4 + * counterparts. + */ + public var projName:String; + /** + * Property: units + * The units of the projection. Values include 'm' and 'degrees' + /** + * Property: units + * The units of the projection. Values include 'm' and 'degrees' + */ + public var units:String; + /** + * Property: datum + * The datum specified for the projection + */ + public var datum:Datum; + public var datumCode:String; + public var datumName:String; + public var nagrids:String; + public var ellps:String; + public var a:Number; + public var b:Number; + public var a2:Number; + public var b2:Number; + public var e:Number; + public var es:Number; + public var ep2:Number; + public var rf:Number; + public var long0:Number; + public var lat0:Number; + public var lat1:Number; + public var lat2:Number; + public var lat_ts:Number; + public var alpha:Number; + public var longc:Number; + public var x0:Number; + public var y0:Number; + public var k0:Number; + public var k:Number; + public var R_A:Boolean = false; + public var zone:int; + public var utmSouth:Boolean = false; + public var to_meter:Number; + public var from_greenwich:Number; + public var datum_params:Array; + public var sphere:Boolean = false; + public var ellipseName:String; + + public var srsCode:String; + public var srsAuth:String; + public var srsProjNumber:String; + + public function ProjParams() + { + } + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjSinu.as b/com/gradoservice/proj4as/proj/ProjSinu.as new file mode 100755 index 00000000..c5e7d334 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjSinu.as @@ -0,0 +1,95 @@ +/******************************************************************************* +NAME SINUSOIDAL + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Sinusoidal projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +PROGRAMMER DATE +---------- ---- +D. Steinwand, EROS May, 1991 + +This function was adapted from the Sinusoidal projection code (FORTRAN) in the +General Cartographic Transformation Package software which is available from +the U.S. Geological Survey National Mapping Division. + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. "Software Documentation for GCTP General Cartographic Transformation + Package", U.S. Geological Survey National Mapping Division, May 1982. +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjSinu extends AbstractProjProjection + { + private var R:Number; + public function ProjSinu(data:ProjParams) + { + super(data); + } + + /* Initialize the Sinusoidal projection + ------------------------------------*/ + override public function init():void + { + /* Place parameters in static storage for common use + -------------------------------------------------*/ + this.R = 6370997.0; //Radius of earth + } + + /* Sinusoidal forward equations--mapping lat,long to x,y + -----------------------------------------------------*/ + override public function forward(p:ProjPoint):ProjPoint + { + var x:Number,y:Number,delta_lon:Number; + var lon:Number=p.x; + var lat:Number=p.y; + /* Forward equations + -----------------*/ + delta_lon = ProjConstants.adjust_lon(lon - this.long0); + x = this.R * delta_lon * Math.cos(lat) + this.x0; + y = this.R * lat + this.y0; + + p.x=x; + p.y=y; + return p; + } + + override public function inverse(p:ProjPoint):ProjPoint + { + var lat:Number,temp:Number,lon:Number; + + /* Inverse equations + -----------------*/ + p.x -= this.x0; + p.y -= this.y0; + lat = p.y / this.R; + if (Math.abs(lat) > ProjConstants.HALF_PI) { + trace("sinu:Inv:DataError"); + } + temp = Math.abs(lat) - ProjConstants.HALF_PI; + if (Math.abs(temp) > ProjConstants.EPSLN) { + temp = this.long0+ p.x / (this.R *Math.cos(lat)); + lon = ProjConstants.adjust_lon(temp); + } else { + lon = this.long0; + } + + p.x=lon; + p.y=lat; + return p; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjSoMerc.as b/com/gradoservice/proj4as/proj/ProjSoMerc.as new file mode 100755 index 00000000..ccaaa685 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjSoMerc.as @@ -0,0 +1,135 @@ +/******************************************************************************* +NAME SWISS OBLIQUE MERCATOR + +PURPOSE: Swiss projection. +WARNING: X and Y are inverted (weird) in the swiss coordinate system. Not + here, since we want X to be horizontal and Y vertical. + +ALGORITHM REFERENCES +1. "Formules et constantes pour le Calcul pour la + projection cylindrique conforme Г  axe oblique et pour la transformation entre + des systГЁmes de rГ©fГ©rence". + http://www.swisstopo.admin.ch/internet/swisstopo/fr/home/topics/survey/sys/refsys/switzerland.parsysrelated1.31216.downloadList.77004.DownloadFile.tmp/swissprojectionfr.pdf + +*******************************************************************************/ + + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjSoMerc extends AbstractProjProjection + { + private var lambda0:Number; + private var R:Number; + private var b0:Number; + private var K:Number; + + + public function ProjSoMerc(data:ProjParams) + { + super(data); + } + + + override public function init():void + { + var phy0:Number = this.lat0; + this.lambda0 = this.long0; + var sinPhy0:Number = Math.sin(phy0); + var semiMajorAxis:Number = this.a; + var invF:Number = this.rf; + var flattening:Number = 1 / invF; + var e2:Number = 2 * flattening - Math.pow(flattening, 2); + var e:Number = this.e = Math.sqrt(e2); + this.R = semiMajorAxis * Math.sqrt(1 - e2) / (1 - e2 * Math.pow(sinPhy0, 2.0)); + this.alpha = Math.sqrt(1 + e2 / (1 - e2) * Math.pow(Math.cos(phy0), 4.0)); + this.b0 = Math.asin(sinPhy0 / this.alpha); + this.K = Math.log(Math.tan(Math.PI / 4.0 + this.b0 / 2.0)) + - this.alpha + * Math.log(Math.tan(Math.PI / 4.0 + phy0 / 2.0)) + + this.alpha + * e / 2 + * Math.log((1 + e * sinPhy0) + / (1 - e * sinPhy0)); + } + + + override public function forward(p:ProjPoint):ProjPoint + { + var Sa1:Number = Math.log(Math.tan(Math.PI / 4.0 - p.y / 2.0)); + var Sa2:Number = this.e / 2.0 + * Math.log((1 + this.e * Math.sin(p.y)) + / (1 - this.e * Math.sin(p.y))); + var S:Number = -this.alpha * (Sa1 + Sa2) + this.K; + + // spheric latitude + var b:Number = 2.0 * (Math.atan(Math.exp(S)) - Math.PI / 4.0); + + // spheric longitude + var I:Number = this.alpha * (p.x - this.lambda0); + + // psoeudo equatorial rotation + var rotI:Number = Math.atan(Math.sin(I) + / (Math.sin(this.b0) * Math.tan(b) + + Math.cos(this.b0) * Math.cos(I))); + + var rotB:Number = Math.asin(Math.cos(this.b0) * Math.sin(b) - + Math.sin(this.b0) * Math.cos(b) * Math.cos(I)); + + p.y = this.R / 2.0 + * Math.log((1 + Math.sin(rotB)) / (1 - Math.sin(rotB))) + + this.y0; + p.x = this.R * rotI + this.x0; + return p; + } + + override public function inverse(p:ProjPoint):ProjPoint + { + var Y:Number = p.x - this.x0; + var X:Number = p.y - this.y0; + + var rotI:Number = Y / this.R; + var rotB:Number = 2 * (Math.atan(Math.exp(X / this.R)) - Math.PI / 4.0); + + var b:Number = Math.asin(Math.cos(this.b0) * Math.sin(rotB) + + Math.sin(this.b0) * Math.cos(rotB) * Math.cos(rotI)); + var I:Number = Math.atan(Math.sin(rotI) + / (Math.cos(this.b0) * Math.cos(rotI) - Math.sin(this.b0) + * Math.tan(rotB))); + + var lambda:Number = this.lambda0 + I / this.alpha; + + var S:Number = 0.0; + var phy:Number = b; + var prevPhy:Number = -1000.0; + var iteration:Number = 0; + while (Math.abs(phy - prevPhy) > 0.0000001) + { + if (++iteration > 20) + { + trace("omercFwdInfinity"); + return null; + } + //S = Math.log(Math.tan(Math.PI / 4.0 + phy / 2.0)); + S = 1.0 + / this.alpha + * (Math.log(Math.tan(Math.PI / 4.0 + b / 2.0)) - this.K) + + this.e + * Math.log(Math.tan(Math.PI / 4.0 + + Math.asin(this.e * Math.sin(phy)) + / 2.0)); + prevPhy = phy; + phy = 2.0 * Math.atan(Math.exp(S)) - Math.PI / 2.0; + } + + p.x = lambda; + p.y = phy; + return p; + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjStere.as b/com/gradoservice/proj4as/proj/ProjStere.as new file mode 100755 index 00000000..e03c85d1 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjStere.as @@ -0,0 +1,278 @@ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjStere extends AbstractProjProjection + { + private const TOL:Number=1.e-8; + private const NITER:Number=8; + private const CONV:Number=1.e-10; + private const S_POLE:Number=0; + private const N_POLE:Number=1; + private const OBLIQ:Number=2; + private const EQUIT:Number=3; + + private var akm1:Number; + private var cosph0:Number; + private var cosX1:Number; + private var sinX1:Number; + private var phi0:Number; + private var phits:Number; + private var sinph0:Number; + + public function ProjStere(data:ProjParams) + { + super(data); + } + + private function ssfn_(phit:Number, sinphi:Number, eccen:Number):Number + { + sinphi *= eccen; + return (Math.tan (.5 * (ProjConstants.HALF_PI + phit)) * Math.pow((1. - sinphi) / (1. + sinphi), .5 * eccen)); + } + + + override public function init():void + { + this.phits = this.lat_ts ? this.lat_ts : ProjConstants.HALF_PI; + var t:Number = Math.abs(this.lat0); + if ((Math.abs(t) - ProjConstants.HALF_PI) < ProjConstants.EPSLN) { + this.mode = this.lat0 < 0. ? this.S_POLE : this.N_POLE; + } else { + this.mode = t > ProjConstants.EPSLN ? this.OBLIQ : this.EQUIT; + } + this.phits = Math.abs(this.phits); + if (this.es) { + var X:Number; + + switch (this.mode) { + case this.N_POLE: + case this.S_POLE: + if (Math.abs(this.phits - ProjConstants.HALF_PI) < ProjConstants.EPSLN) { + this.akm1 = 2. * this.k0 / Math.sqrt(Math.pow(1+this.e,1+this.e)*Math.pow(1-this.e,1-this.e)); + } else { + t = Math.sin(this.phits); + this.akm1 = Math.cos(this.phits) / ProjConstants.tsfnz(this.e, this.phits, t); + t *= this.e; + this.akm1 /= Math.sqrt(1. - t * t); + } + break; + case this.EQUIT: + this.akm1 = 2. * this.k0; + break; + case this.OBLIQ: + t = Math.sin(this.lat0); + X = 2. * Math.atan(this.ssfn_(this.lat0, t, this.e)) - ProjConstants.HALF_PI; + t *= this.e; + this.akm1 = 2. * this.k0 * Math.cos(this.lat0) / Math.sqrt(1. - t * t); + this.sinX1 = Math.sin(X); + this.cosX1 = Math.cos(X); + break; + } + } else { + switch (this.mode) { + case this.OBLIQ: + this.sinph0 = Math.sin(this.lat0); + this.cosph0 = Math.cos(this.lat0); + case this.EQUIT: + this.akm1 = 2. * this.k0; + break; + case this.S_POLE: + case this.N_POLE: + this.akm1 = Math.abs(this.phits - ProjConstants.HALF_PI) >= ProjConstants.EPSLN ? + Math.cos(this.phits) / Math.tan(ProjConstants.FORTPI - .5 * this.phits) : + 2. * this.k0 ; + break; + } + } + } + + // Stereographic forward equations--mapping lat,long to x,y + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number = p.x; + var lat:Number = p.y; + var x:Number, y:Number, A:Number, X:Number; + var sinX:Number,cosX:Number; + + if (this.sphere) { + var sinphi:Number, cosphi:Number, coslam:Number, sinlam:Number; + + sinphi = Math.sin(lat); + cosphi = Math.cos(lat); + coslam = Math.cos(lon); + sinlam = Math.sin(lon); + switch (this.mode) { + case this.EQUIT: + y = 1. + cosphi * coslam; + if (y <= ProjConstants.EPSLN) { + trace('ERROR'); + //F_ERROR; + } + y = this.akm1 / y; + x = y * cosphi * sinlam; + y *= sinphi; + break; + case this.OBLIQ: + y = 1. + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam; + if (y <= ProjConstants.EPSLN) { + trace('ERROR'); + //F_ERROR; + } + y = this.akm1 / y; + x = y * cosphi * sinlam; + y *= this.cosph0 * sinphi - this.sinph0 * cosphi * coslam; + break; + case this.N_POLE: + coslam = -coslam; + lat = -lat; + //Note no break here so it conitnues through S_POLE + case this.S_POLE: + if (Math.abs(lat - ProjConstants.HALF_PI) < this.TOL) { + trace('ERROR'); + //F_ERROR; + } + y = this.akm1 * Math.tan(ProjConstants.FORTPI + .5 * lat) + x = sinlam * y; + y *= coslam; + break; + } + } else { + coslam = Math.cos(lon); + sinlam = Math.sin(lon); + sinphi = Math.sin(lat); + if (this.mode == this.OBLIQ || this.mode == this.EQUIT) { + X = 2. * Math.atan(this.ssfn_(lat, sinphi, this.e)); + sinX = Math.sin(X - ProjConstants.HALF_PI); + cosX = Math.cos(X); + } + switch (this.mode) { + case this.OBLIQ: + A = this.akm1 / (this.cosX1 * (1. + this.sinX1 * sinX + this.cosX1 * cosX * coslam)); + y = A * (this.cosX1 * sinX - this.sinX1 * cosX * coslam); + x = A * cosX; + break; + case this.EQUIT: + A = 2. * this.akm1 / (1. + cosX * coslam); + y = A * sinX; + x = A * cosX; + break; + case this.S_POLE: + lat = -lat; + coslam = - coslam; + sinphi = -sinphi; + case this.N_POLE: + x = this.akm1 * ProjConstants.tsfnz(this.e, lat, sinphi); + y = - x * coslam; + break; + } + x = x * sinlam; + } + p.x = x*this.a + this.x0; + p.y = y*this.a + this.y0; + return p; + } + + + //* Stereographic inverse equations--mapping x,y to lat/long + override public function inverse(p:ProjPoint):ProjPoint + { + var x:Number = (p.x - this.x0)/this.a; /* descale and de-offset */ + var y:Number = (p.y - this.y0)/this.a; + var lon:Number, lat:Number; + + var cosphi:Number, sinphi:Number, tp:Number=0.0, phi_l:Number=0.0, rho:Number, halfe:Number=0.0, pi2:Number=0.0; + var i:int; + + if (this.sphere) { + var c:Number, rh:Number, sinc:Number, cosc:Number; + + rh = Math.sqrt(x*x + y*y); + c = 2. * Math.atan(rh / this.akm1); + sinc = Math.sin(c); + cosc = Math.cos(c); + lon = 0.; + switch (this.mode) { + case this.EQUIT: + if (Math.abs(rh) <= ProjConstants.EPSLN) { + lat = 0.; + } else { + lat = Math.asin(y * sinc / rh); + } + if (cosc != 0. || x != 0.) lon = Math.atan2(x * sinc, cosc * rh); + break; + case this.OBLIQ: + if (Math.abs(rh) <= ProjConstants.EPSLN) { + lat = this.phi0; + } else { + lat = Math.asin(cosc * sinph0 + y * sinc * cosph0 / rh); + } + c = cosc - sinph0 * Math.sin(lat); + if (c != 0. || x != 0.) { + lon = Math.atan2(x * sinc * cosph0, c * rh); + } + break; + case this.N_POLE: + y = -y; + case this.S_POLE: + if (Math.abs(rh) <= ProjConstants.EPSLN) { + lat = this.phi0; + } else { + lat = Math.asin(this.mode == this.S_POLE ? -cosc : cosc); + } + lon = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y); + break; + } + } else { + rho = Math.sqrt(x*x + y*y); + switch (this.mode) { + case this.OBLIQ: + case this.EQUIT: + tp = 2. * Math.atan2(rho * this.cosX1 , this.akm1); + cosphi = Math.cos(tp); + sinphi = Math.sin(tp); + if( rho == 0.0 ) { + phi_l = Math.asin(cosphi * this.sinX1); + } else { + phi_l = Math.asin(cosphi * this.sinX1 + (y * sinphi * this.cosX1 / rho)); + } + + tp = Math.tan(.5 * (ProjConstants.HALF_PI + phi_l)); + x *= sinphi; + y = rho * this.cosX1 * cosphi - y * this.sinX1* sinphi; + pi2 = ProjConstants.HALF_PI; + halfe = .5 * this.e; + break; + case this.N_POLE: + y = -y; + case this.S_POLE: + tp = - rho / this.akm1 + phi_l = ProjConstants.HALF_PI - 2. * Math.atan(tp); + pi2 = -ProjConstants.HALF_PI; + halfe = -.5 * this.e; + break; + } + for (i = this.NITER; i--; phi_l = lat) + { //check this + sinphi = this.e * Math.sin(phi_l); + lat = 2. * Math.atan(tp * Math.pow((1.+sinphi)/(1.-sinphi), halfe)) - pi2; + if (Math.abs(phi_l - lat) < this.CONV) + { + if (this.mode == this.S_POLE) lat = -lat; + lon = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y); + p.x = lon; + p.y = lat + return p; + } + } + } + return null; + } + + + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjSterea.as b/com/gradoservice/proj4as/proj/ProjSterea.as new file mode 100755 index 00000000..49b34599 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjSterea.as @@ -0,0 +1,74 @@ +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjSterea extends ProjGauss + { + + protected var sinc0:Number; + protected var cosc0:Number; + protected var R2:Number; + + public function ProjSterea(data:ProjParams) + { + super(data); + } + + override public function init():void + { + super.init(); + if (!this.rc) { + trace("sterea:init:E_ERROR_0"); + return; + } + this.sinc0 = Math.sin(this.phic0); + this.cosc0 = Math.cos(this.phic0); + this.R2 = 2.0 * this.rc; + if (!this.title) this.title = "Oblique Stereographic Alternative"; + } + + override public function forward(p:ProjPoint):ProjPoint + { + p.x = ProjConstants.adjust_lon(p.x-this.long0); /* adjust del longitude */ + super.forward(p); + var sinc:Number = Math.sin(p.y); + var cosc:Number = Math.cos(p.y); + var cosl:Number = Math.cos(p.x); + k = this.k0 * this.R2 / (1.0 + this.sinc0 * sinc + this.cosc0 * cosc * cosl); + p.x = k * cosc * Math.sin(p.x); + p.y = k * (this.cosc0 * sinc - this.sinc0 * cosc * cosl); + p.x = this.a * p.x + this.x0; + p.y = this.a * p.y + this.y0; + return p; + } + + override public function inverse(p:ProjPoint):ProjPoint + { + var lon:Number,lat:Number,rho:Number,sinc:Number,cosc:Number; + p.x = (p.x - this.x0) / this.a; /* descale and de-offset */ + p.y = (p.y - this.y0) / this.a; + + p.x /= this.k0; + p.y /= this.k0; + if ( (rho = Math.sqrt(p.x*p.x + p.y*p.y)) ) { + c = 2.0 * Math.atan2(rho, this.R2); + sinc = Math.sin(c); + cosc = Math.cos(c); + lat = Math.asin(cosc * this.sinc0 + p.y * sinc * this.cosc0 / rho); + lon = Math.atan2(p.x * sinc, rho * this.cosc0 * cosc - p.y * this.sinc0 * sinc); + } else { + lat = this.phic0; + lon = 0.; + } + + p.x = lon; + p.y = lat; + super.inverse(p); + p.x = ProjConstants.adjust_lon(p.x + this.long0); /* adjust longitude to CM */ + return p; + } + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjTmerc.as b/com/gradoservice/proj4as/proj/ProjTmerc.as new file mode 100755 index 00000000..b6d083ac --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjTmerc.as @@ -0,0 +1,154 @@ +/******************************************************************************* +NAME TRANSVERSE MERCATOR + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Transverse Mercator projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government + Printing Office, Washington D.C., 1989. +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjTmerc extends AbstractProjProjection + { + public function ProjTmerc(data:ProjParams) + { + super(data); + } + + override public function init():void + { + this.e0 = ProjConstants.e0fn(this.es); + this.e1 = ProjConstants.e1fn(this.es); + this.e2 = ProjConstants.e2fn(this.es); + this.e3 = ProjConstants.e3fn(this.es); + this.ml0 = this.a * ProjConstants.mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0); + } + + /** + Transverse Mercator Forward - long/lat to x/y + long/lat in radians + */ + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number = p.x; + var lat:Number = p.y; + + var delta_lon:Number = ProjConstants.adjust_lon(lon - this.long0); // Delta longitude + var con:Number; // cone constant + var x:Number, y:Number; + var sin_phi:Number=Math.sin(lat); + var cos_phi:Number=Math.cos(lat); + + if (this.sphere) { /* spherical form */ + var b:Number = cos_phi * Math.sin(delta_lon); + if ((Math.abs(Math.abs(b) - 1.0)) < .0000000001) { + trace("tmerc:forward: Point projects into infinity"); + return null; + } else { + x = .5 * this.a * this.k0 * Math.log((1.0 + b)/(1.0 - b)); + con = Math.acos(cos_phi * Math.cos(delta_lon)/Math.sqrt(1.0 - b*b)); + if (lat < 0) con = - con; + y = this.a * this.k0 * (con - this.lat0); + } + } else { + var al:Number = cos_phi * delta_lon; + var als:Number = Math.pow(al,2); + var c:Number = this.ep2 * Math.pow(cos_phi,2); + var tq:Number = Math.tan(lat); + var t:Number = Math.pow(tq,2); + con = 1.0 - this.es * Math.pow(sin_phi,2); + var n:Number = this.a / Math.sqrt(con); + var ml:Number = this.a * ProjConstants.mlfn(this.e0, this.e1, this.e2, this.e3, lat); + + x = this.k0 * n * al * (1.0 + als / 6.0 * (1.0 - t + c + als / 20.0 * (5.0 - 18.0 * t + Math.pow(t,2) + 72.0 * c - 58.0 * this.ep2))) + this.x0; + y = this.k0 * (ml - this.ml0 + n * tq * (als * (0.5 + als / 24.0 * (5.0 - t + 9.0 * c + 4.0 * Math.pow(c,2) + als / 30.0 * (61.0 - 58.0 * t + Math.pow(t,2) + 600.0 * c - 330.0 * this.ep2))))) + this.y0; + + } + p.x = x; p.y = y; + return p; + } // tmercFwd() + + /** + Transverse Mercator Inverse - x/y to long/lat + */ + override public function inverse(p:ProjPoint):ProjPoint + { + var con:Number, phi:Number; /* temporary angles */ + var delta_phi:Number; /* difference between longitudes */ + var i:Number; + var max_iter:Number = 6; /* maximun number of iterations */ + var lat:Number, lon:Number; + + if (this.sphere) { /* spherical form */ + var f:Number = Math.exp(p.x/(this.a * this.k0)); + var g:Number = .5 * (f - 1/f); + var temp:Number = this.lat0 + p.y/(this.a * this.k0); + var h:Number = Math.cos(temp); + con = Math.sqrt((1.0 - h * h)/(1.0 + g * g)); + lat = ProjConstants.asinz(con); + if (temp < 0) + lat = -lat; + if ((g == 0) && (h == 0)) { + lon = this.long0; + } else { + lon = ProjConstants.adjust_lon(Math.atan2(g,h) + this.long0); + } + } else { // ellipsoidal form + var x:Number= p.x - this.x0; + var y:Number = p.y - this.y0; + + con = (this.ml0 + y / this.k0) / this.a; + phi = con; + for (i=0;;i++) { + delta_phi=((con + this.e1 * Math.sin(2.0*phi) - this.e2 * Math.sin(4.0*phi) + this.e3 * Math.sin(6.0*phi)) / this.e0) - phi; + phi += delta_phi; + if (Math.abs(delta_phi) <= ProjConstants.EPSLN) break; + if (i >= max_iter) { + trace("tmerc:inverse: Latitude failed to converge"); + return null; + } + } // for() + if (Math.abs(phi) < ProjConstants.HALF_PI) { + // sincos(phi, &sin_phi, &cos_phi); + var sin_phi:Number=Math.sin(phi); + var cos_phi:Number=Math.cos(phi); + var tan_phi:Number = Math.tan(phi); + var c:Number = this.ep2 * Math.pow(cos_phi,2); + var cs:Number = Math.pow(c,2); + var t:Number = Math.pow(tan_phi,2); + var ts :Number= Math.pow(t,2); + con = 1.0 - this.es * Math.pow(sin_phi,2); + var n:Number = this.a / Math.sqrt(con); + var r:Number = n * (1.0 - this.es) / con; + var d:Number = x / (n * this.k0); + var ds:Number = Math.pow(d,2); + lat = phi - (n * tan_phi * ds / r) * (0.5 - ds / 24.0 * (5.0 + 3.0 * t + 10.0 * c - 4.0 * cs - 9.0 * this.ep2 - ds / 30.0 * (61.0 + 90.0 * t + 298.0 * c + 45.0 * ts - 252.0 * this.ep2 - 3.0 * cs))); + lon = ProjConstants.adjust_lon(this.long0 + (d * (1.0 - ds / 6.0 * (1.0 + 2.0 * t + c - ds / 20.0 * (5.0 - 2.0 * c + 28.0 * t - 3.0 * cs + 8.0 * this.ep2 + 24.0 * ts))) / cos_phi)); + } else { + lat = ProjConstants.HALF_PI * ProjConstants.sign(y); + lon = this.long0; + } + } + p.x = lon; + p.y = lat; + return p; + } // tmercInv() + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjUtm.as b/com/gradoservice/proj4as/proj/ProjUtm.as new file mode 100755 index 00000000..5e07f733 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjUtm.as @@ -0,0 +1,50 @@ +/******************************************************************************* +NAME TRANSVERSE MERCATOR + +PURPOSE: Transforms input longitude and latitude to Easting and + Northing for the Transverse Mercator projection. The + longitude and latitude must be in radians. The Easting + and Northing values will be returned in meters. + +ALGORITHM REFERENCES + +1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", + U.S. Geological Survey Professional Paper 1453 , United State Government + Printing Office, Washington D.C., 1989. +*******************************************************************************/ + + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjUtm extends ProjTmerc + { + public function ProjUtm(data:ProjParams) + { + super(data); + } + + override public function init():void + { + if (!this.zone) { + trace("utm:init: zone must be specified for UTM"); + return; + } + this.lat0 = 0.0; + this.long0 = ((6 * Math.abs(this.zone)) - 183) * ProjConstants.D2R; + this.x0 = 500000.0; + this.y0 = this.utmSouth ? 10000000.0 : 0.0; + this.k0 = 0.9996; + + super.init(); + } + + + } +} \ No newline at end of file diff --git a/com/gradoservice/proj4as/proj/ProjVandg.as b/com/gradoservice/proj4as/proj/ProjVandg.as new file mode 100755 index 00000000..0a0cdbd4 --- /dev/null +++ b/com/gradoservice/proj4as/proj/ProjVandg.as @@ -0,0 +1,154 @@ +/******************************************************************************* +NAME VAN DER GRINTEN + +PURPOSE: Transforms input Easting and Northing to longitude and + latitude for the Van der Grinten projection. The + Easting and Northing must be in meters. The longitude + and latitude values will be returned in radians. + +PROGRAMMER DATE +---------- ---- +T. Mittan March, 1993 + +This function was adapted from the Van Der Grinten projection code +(FORTRAN) in the General Cartographic Transformation Package software +which is available from the U.S. Geological Survey National Mapping Division. + +ALGORITHM REFERENCES + +1. "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder, + The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355. + +2. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological + Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United + State Government Printing Office, Washington D.C., 1987. + +3. "Software Documentation for GCTP General Cartographic Transformation + Package", U.S. Geological Survey National Mapping Division, May 1982. +*******************************************************************************/ + +package com.gradoservice.proj4as.proj +{ + import com.gradoservice.proj4as.ProjPoint; + import com.gradoservice.proj4as.ProjConstants; + import com.gradoservice.proj4as.Datum; + + public class ProjVandg extends AbstractProjProjection + { + + protected var R:Number; + + public function ProjVandg(data:ProjParams) + { + super(data); + } + + override public function init():void + { + this.R = 6370997.0; //Radius of earth + } + + override public function forward(p:ProjPoint):ProjPoint + { + var lon:Number=p.x; + var lat:Number=p.y; + + /* Forward equations + -----------------*/ + var dlon:Number = ProjConstants.adjust_lon(lon - this.long0); + var x:Number,y:Number; + + if (Math.abs(lat) <= ProjConstants.EPSLN) { + x = this.x0 + this.R * dlon; + y = this.y0; + } + var theta:Number = ProjConstants.asinz(2.0 * Math.abs(lat / ProjConstants.PI)); + if ((Math.abs(dlon) <= ProjConstants.EPSLN) || (Math.abs(Math.abs(lat) - ProjConstants.HALF_PI) <= ProjConstants.EPSLN)) { + x = this.x0; + if (lat >= 0) { + y = this.y0 + ProjConstants.PI * this.R * Math.tan(.5 * theta); + } else { + y = this.y0 + ProjConstants.PI * this.R * - Math.tan(.5 * theta); + } + // return(OK); + } + var al:Number = .5 * Math.abs((ProjConstants.PI / dlon) - (dlon / ProjConstants.PI)); + var asq:Number = al * al; + var sinth:Number = Math.sin(theta); + var costh:Number = Math.cos(theta); + + var g :Number= costh / (sinth + costh - 1.0); + var gsq:Number = g * g; + var m:Number = g * (2.0 / sinth - 1.0); + var msq:Number = m * m; + var con:Number = ProjConstants.PI * this.R * (al * (g - msq) + Math.sqrt(asq * (g - msq) * (g - msq) - (msq + asq) * (gsq - msq))) / (msq + asq); + if (dlon < 0) { + con = -con; + } + x = this.x0 + con; + con = Math.abs(con / (ProjConstants.PI * this.R)); + if (lat >= 0) { + y = this.y0 + ProjConstants.PI * this.R * Math.sqrt(1.0 - con * con - 2.0 * al * con); + } else { + y = this.y0 - ProjConstants.PI * this.R * Math.sqrt(1.0 - con * con - 2.0 * al * con); + } + p.x = x; + p.y = y; + return p; + } + +/* Van Der Grinten inverse equations--mapping x,y to lat/long + ---------------------------------------------------------*/ + override public function inverse(p:ProjPoint):ProjPoint + { + var dlon:Number,lat:Number,lon:Number; + var xx:Number,yy:Number,xys:Number,c1:Number,c2:Number,c3:Number; + var al:Number,asq:Number; + var a1:Number; + var m1:Number; + var con:Number; + var th1:Number; + var d:Number; + + /* inverse equations + -----------------*/ + p.x -= this.x0; + p.y -= this.y0; + con = ProjConstants.PI * this.R; + xx = p.x / con; + yy =p.y / con; + xys = xx * xx + yy * yy; + c1 = -Math.abs(yy) * (1.0 + xys); + c2 = c1 - 2.0 * yy * yy + xx * xx; + c3 = -2.0 * c1 + 1.0 + 2.0 * yy * yy + xys * xys; + d = yy * yy / c3 + (2.0 * c2 * c2 * c2 / c3 / c3 / c3 - 9.0 * c1 * c2 / c3 /c3) / 27.0; + a1 = (c1 - c2 * c2 / 3.0 / c3) / c3; + m1 = 2.0 * Math.sqrt( -a1 / 3.0); + con = ((3.0 * d) / a1) / m1; + if (Math.abs(con) > 1.0) { + if (con >= 0.0) { + con = 1.0; + } else { + con = -1.0; + } + } + th1 = Math.acos(con) / 3.0; + if (p.y >= 0) { + lat = (-m1 *Math.cos(th1 + ProjConstants.PI / 3.0) - c2 / 3.0 / c3) * ProjConstants.PI; + } else { + lat = -(-m1 * Math.cos(th1 + Math.PI / 3.0) - c2 / 3.0 / c3) * ProjConstants.PI; + } + + if (Math.abs(xx) < ProjConstants.EPSLN) { + lon = this.long0; + } + lon = ProjConstants.adjust_lon(this.long0 + ProjConstants.PI * (xys - 1.0 + Math.sqrt(1.0 + 2.0 * (xx * xx - yy * yy) + xys * xys)) / 2.0 / xx); + + p.x=lon; + p.y=lat; + return p; + } + + + } +} \ No newline at end of file diff --git a/embedded/warning.png b/embedded/warning.png new file mode 100644 index 00000000..a9e4ff39 Binary files /dev/null and b/embedded/warning.png differ diff --git a/halcyon_viewer.as b/halcyon_viewer.as index 3b01b9e9..cbf3c914 100644 --- a/halcyon_viewer.as +++ b/halcyon_viewer.as @@ -69,7 +69,7 @@ package { } private function onRefreshCSS(str:String):void { - theMap.setStyle(str); + theMap.editableLayer.setStyle(str); } private function onJumpTo(lat:Number,lon:Number):void { theMap.init(lat,lon); diff --git a/net/systemeD/controls/AutoComplete.as b/net/systemeD/controls/AutoComplete.as index 527b8049..833e6274 100644 --- a/net/systemeD/controls/AutoComplete.as +++ b/net/systemeD/controls/AutoComplete.as @@ -355,6 +355,7 @@ package net.systemeD.controls { } private function defaultFilterFunction(element:*, text:String):Boolean { + if (!text || text=='') return false; var label:String = itemToLabel(element); return (label.toLowerCase().substring(0,text.length) == text.toLowerCase()); } diff --git a/net/systemeD/controls/DataGridWarningField.as b/net/systemeD/controls/DataGridWarningField.as new file mode 100644 index 00000000..647a614b --- /dev/null +++ b/net/systemeD/controls/DataGridWarningField.as @@ -0,0 +1,42 @@ +package net.systemeD.controls { + import mx.controls.Label; + import mx.controls.listClasses.*; + import flash.display.DisplayObject; + import mx.controls.Image; + + public class DataGridWarningField extends Label { + + private var _image:Image; + [Embed(source="../../../embedded/warning.png")] private var warningIcon:Class; + + function DataGridWarningField():void { + super(); + setStyle('paddingLeft',2); + } + + override protected function createChildren():void { + super.createChildren(); + _image = new Image(); + _image.source = warningIcon; + _image.width = 16; + _image.height = 16; + addChild(DisplayObject(_image)); + } + + override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { + super.updateDisplayList(unscaledWidth, unscaledHeight); + + if (data.value.indexOf(';')>-1) { + setStyle('color',0xFF0000); + _image.visible=true; + _image.x = width -_image.width -5; + _image.y = height-_image.height-3; + _image.toolTip = "The tag contains more than one value - please check"; + textField.width = width-_image.width-5; + } else { + setStyle('color',0); + _image.visible=false; + } + } + } +} diff --git a/net/systemeD/controls/PromptingTextInputWarning.as b/net/systemeD/controls/PromptingTextInputWarning.as new file mode 100644 index 00000000..36616468 --- /dev/null +++ b/net/systemeD/controls/PromptingTextInputWarning.as @@ -0,0 +1,39 @@ +package net.systemeD.controls { + import flexlib.controls.PromptingTextInput; + import flash.display.DisplayObject; + import mx.controls.Image; + + public class PromptingTextInputWarning extends PromptingTextInput { + + private var _image:Image; + [Embed(source="../../../embedded/warning.png")] private var warningIcon:Class; + + function PromptingTextInputWarning():void { + super(); + } + + override protected function createChildren():void { + super.createChildren(); + _image = new Image(); + _image.source = warningIcon; + _image.width = 16; + _image.height = 16; + addChild(DisplayObject(_image)); + } + + override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { + super.updateDisplayList(unscaledWidth, unscaledHeight); + if (text && text.indexOf(';')>-1) { + setStyle('color',0xFF0000); + _image.visible=true; + _image.x = width -_image.width -5; + _image.y = height-_image.height-3; + _image.toolTip = "The tag contains more than one value - please check"; + textField.width = width-_image.width-5; + } else { + setStyle('color',0); + _image.visible=false; + } + } + } +} diff --git a/net/systemeD/halcyon/Elastic.as b/net/systemeD/halcyon/Elastic.as index f0daa9d4..7daf83cd 100644 --- a/net/systemeD/halcyon/Elastic.as +++ b/net/systemeD/halcyon/Elastic.as @@ -17,14 +17,16 @@ package net.systemeD.halcyon { public var map:Map; // reference to parent map public var sprites:Array=new Array(); // instances in display list + private var editableLayer:MapPaint; private var _start:Point; private var _end:Point; /** Create and draw the elastic. */ public function Elastic(map:Map, start:Point, end:Point) { this.map = map; - this._start = start; - this._end = end; + editableLayer = map.editableLayer; + _start = start; + _end = end; redraw(); } @@ -62,7 +64,7 @@ package net.systemeD.halcyon { var stroke:Shape = new Shape(); stroke.graphics.lineStyle(1, 0xff0000, 1, false, "normal", CapsStyle.ROUND, JointStyle.ROUND); - var l:DisplayObject=map.paint.getPaintSpriteAt(map.paint.maxlayer); + var l:DisplayObject=editableLayer.getPaintSpriteAt(editableLayer.maxlayer); var o:DisplayObject=Sprite(l).getChildAt(3); // names layer (o as Sprite).addChild(stroke); sprites.push(stroke); diff --git a/net/systemeD/halcyon/EntityUI.as b/net/systemeD/halcyon/EntityUI.as index d5fb8f4c..bca513b3 100644 --- a/net/systemeD/halcyon/EntityUI.as +++ b/net/systemeD/halcyon/EntityUI.as @@ -55,6 +55,7 @@ package net.systemeD.halcyon { this.entity=entity; this.paint=paint; entity.addEventListener(Connection.TAG_CHANGED, tagChanged, false, 0, true); + entity.addEventListener(Connection.STATUS_CHANGED, statusChanged, false, 0, true); entity.addEventListener(Connection.ADDED_TO_RELATION, relationAdded, false, 0, true); entity.addEventListener(Connection.REMOVED_FROM_RELATION, relationRemoved, false, 0, true); entity.addEventListener(Connection.SUSPEND_REDRAW, suspendRedraw, false, 0, true); @@ -71,6 +72,7 @@ package net.systemeD.halcyon { /** Remove the default event listeners. */ protected function removeGenericEventListeners():void { entity.removeEventListener(Connection.TAG_CHANGED, tagChanged); + entity.removeEventListener(Connection.STATUS_CHANGED, statusChanged); entity.removeEventListener(Connection.ADDED_TO_RELATION, relationAdded); entity.removeEventListener(Connection.REMOVED_FROM_RELATION, relationRemoved); entity.removeEventListener(Connection.SUSPEND_REDRAW, suspendRedraw); @@ -123,6 +125,11 @@ package net.systemeD.halcyon { redraw(); } + protected function statusChanged(event:EntityEvent):void { + invalidateStyleList(); + redraw(); + } + protected function mouseEvent(event:MouseEvent):void { paint.map.entityMouseEvent(event, entity); } @@ -132,13 +139,13 @@ package net.systemeD.halcyon { /** Add object (stroke/fill/roadname) to layer sprite*/ - protected function addToLayer(s:DisplayObject,t:uint,sublayer:int=-1):void { + protected function addToLayer(s:DisplayObject,spritetype:uint,sublayer:int=-1):void { var l:Sprite, o:Sprite; if (sublayer!=-1) { - o=paint.sublayer(layer,sublayer); + o=paint.sublayer(layer,spritetype,sublayer); } else { l=paint.getPaintSpriteAt(layer); - o=l.getChildAt(t) as Sprite; + o=l.getChildAt(spritetype) as Sprite; } o.addChild(s); if (sprites.indexOf(s)==-1) { sprites.push(s); } @@ -148,7 +155,6 @@ package net.systemeD.halcyon { } } - // What does this do, could someone please document? protected function setListenSprite():void { var l:Sprite=paint.getHitSpriteAt(layer); var s:Sprite; diff --git a/net/systemeD/halcyon/Map.as b/net/systemeD/halcyon/Map.as index ffc920a0..8a45b074 100644 --- a/net/systemeD/halcyon/Map.as +++ b/net/systemeD/halcyon/Map.as @@ -28,10 +28,8 @@ package net.systemeD.halcyon { /** don't zoom in past this */ public const MAXSCALE:uint=23; - /** sprite for ways and (POI/tagged) nodes in core layer */ - public var paint:MapPaint; - /** sprite for vector background layers */ - public var vectorbg:Sprite; + // Container for MapPaint objects + private var paintContainer:Sprite; /** map scale */ public var scale:uint=14; @@ -69,106 +67,54 @@ package net.systemeD.halcyon { /** How far the map can be dragged without actually triggering a pan. */ public const TOLERANCE:uint=7; // | - /** object containing HTML page parameters */ + /** object containing HTML page parameters: lat, lon, zoom, background_dim, background_sharpen, tileblocks */ public var initparams:Object; /** reference to backdrop sprite */ public var backdrop:Object; /** background tile object */ public var tileset:TileSet; - /** background tile URL, name and scheme */ - private var tileparams:Object={ url:'' }; - /** internal style URL */ - private var styleurl:String=''; /** show all objects, even if unstyled? */ public var showall:Boolean=true; - /** server connection */ - public var connection:Connection; - /** VectorLayer objects */ - public var vectorlayers:Object={}; - // ------------------------------------------------------------------------------------------ /** Map constructor function */ - public function Map(initparams:Object) { + public function Map() { + // Remove any existing sprites + while (numChildren) { removeChildAt(0); } - this.initparams=initparams; - connection = Connection.getConnection(initparams); - connection.addEventListener(Connection.NEW_WAY, newWayCreated); - connection.addEventListener(Connection.NEW_POI, newPOICreated); - connection.addEventListener(Connection.WAY_RENUMBERED, wayRenumbered); - connection.addEventListener(Connection.NODE_RENUMBERED, nodeRenumbered); - gotEnvironment(null); + // 900913 background + tileset=new TileSet(this); + addChild(tileset); + + // Container for all MapPaint objects + paintContainer = new Sprite(); + addChild(paintContainer); addEventListener(Event.ENTER_FRAME, everyFrame); scrollRect=new Rectangle(0,0,800,600); - } - public function gotEnvironment(r:Object):void { - var loader:Loader = new Loader(); - loader.contentLoaderInfo.addEventListener(Event.COMPLETE, gotFont); - loader.load(new URLRequest("FontLibrary.swf")); - } - - public function gotFont(r:Event):void { - var FontLibrary:Class = r.target.applicationDomain.getDefinition("FontLibrary") as Class; - Font.registerFont(FontLibrary.DejaVu); - - if (initparams['lat'] != null) { - // parameters sent from HTML - init(initparams['lat'], - initparams['lon'], - initparams['zoom']); - - } else { - // somewhere innocuous - init(53.09465,-2.56495,17); + if (ExternalInterface.available) { + ExternalInterface.addCallback("setPosition", function (lat:Number,lon:Number,zoom:uint):void { + updateCoordsFromLatLon(lat, lon); + changeScale(zoom); + }); } } // ------------------------------------------------------------------------------------------ /** Initialise map at a given lat/lon */ public function init(startlat:Number, startlon:Number, startscale:uint=0):void { - while (numChildren) { removeChildAt(0); } - - tileset=new TileSet(this); // 0 - 900913 background - if (initparams['tileblocks']) { // | option to block dodgy tile sources - tileset.blocks=initparams['tileblocks'];// | - } // | - addChild(tileset); // | - tileset.init(tileparams, false, - initparams['background_dim'] ==null ? true : initparams['background_dim'], - initparams['background_sharpen']==null ? false : initparams['background_sharpen']); - - vectorbg = new Sprite(); // 1 - vector background layers - addChild(vectorbg); // | - - paint = new MapPaint(this,-5,5); // 2 - core paint object - addChild(paint); // | - paint.isBackground=false; // | - - if (styleurl) { // if we've only just set up paint, then setStyle won't have created the RuleSet - paint.ruleset=new RuleSet(MINSCALE,MAXSCALE,redraw,redrawPOIs); - paint.ruleset.loadFromCSS(styleurl); - } if (startscale>0) { scale=startscale; this.dispatchEvent(new MapEvent(MapEvent.SCALE, {scale:scale})); } - scalefactor=MASTERSCALE/Math.pow(2,13-scale); baselon =startlon -(mapwidth /2)/scalefactor; basey =lat2latp(startlat)+(mapheight/2)/scalefactor; updateCoords(0,0); this.dispatchEvent(new Event(MapEvent.INITIALISED)); download(); - - if (ExternalInterface.available) { - ExternalInterface.addCallback("setPosition", function (lat:Number,lon:Number,zoom:uint):void { - updateCoordsFromLatLon(lat, lon); - changeScale(zoom); - }); - } } // ------------------------------------------------------------------------------------------ @@ -220,14 +166,14 @@ package net.systemeD.halcyon { private function moveMap(dx:Number,dy:Number):void { updateCoords(getX()+dx,getY()+dy); - updateEntityUIs(false, false); + updateAllEntityUIs(false, false); download(); } /** Recentre map at given lat/lon, updating the UI and downloading entities. */ public function moveMapFromLatLon(lat:Number,lon:Number):void { updateCoordsFromLatLon(lat,lon); - updateEntityUIs(false,false); + updateAllEntityUIs(false,false); download(); } @@ -236,7 +182,6 @@ package net.systemeD.halcyon { if (lat> edge_t || lat < edge_b || lon < edge_l || lon > edge_r) { moveMapFromLatLon(lat, lon); } - } // Co-ordinate conversion functions @@ -278,79 +223,8 @@ package net.systemeD.halcyon { */ public function download():void { this.dispatchEvent(new MapEvent(MapEvent.DOWNLOAD, {minlon:edge_l, maxlon:edge_r, maxlat:edge_t, minlat:edge_b} )); - connection.loadBbox(edge_l,edge_r,edge_t,edge_b); - - // Do the same for vector layers - for each (var layer:VectorLayer in vectorlayers) { - layer.loadBbox(edge_l,edge_r,edge_t,edge_b); - } - } - - private function newWayCreated(event:EntityEvent):void { - var way:Way = event.entity as Way; - if (!way.loaded || !way.within(edge_l,edge_r,edge_t,edge_b)) { return; } - paint.createWayUI(way); - } - - private function newPOICreated(event:EntityEvent):void { - var node:Node = event.entity as Node; - if (!node.within(edge_l,edge_r,edge_t,edge_b)) { return; } - paint.createNodeUI(node); - } - - private function wayRenumbered(event:EntityRenumberedEvent):void { - var way:Way = event.entity as Way; - paint.renumberWayUI(way,event.oldID); - } - - private function nodeRenumbered(event:EntityRenumberedEvent):void { - var node:Node = event.entity as Node; - paint.renumberNodeUI(node,event.oldID); - } - - /** Visually mark an entity as highlighted. */ - public function setHighlight(entity:Entity, settings:Object):void { - if ( entity is Way && paint.wayuis[entity.id] ) { paint.wayuis[entity.id].setHighlight(settings); } - else if ( entity is Node && paint.nodeuis[entity.id]) { paint.nodeuis[entity.id].setHighlight(settings); } - } - - public function setHighlightOnNodes(way:Way, settings:Object):void { - if (paint.wayuis[way.id]) paint.wayuis[way.id].setHighlightOnNodes(settings); - } - - public function protectWay(way:Way):void { - if (paint.wayuis[way.id]) paint.wayuis[way.id].protectSprites(); - } - - public function unprotectWay(way:Way):void { - if (paint.wayuis[way.id]) paint.wayuis[way.id].unprotectSprites(); - } - - public function limitWayDrawing(way:Way,except:Number=NaN,only:Number=NaN):void { - if (!paint.wayuis[way.id]) return; - paint.wayuis[way.id].drawExcept=except; - paint.wayuis[way.id].drawOnly =only; - paint.wayuis[way.id].redraw(); - } - - /** Protect Entities and EntityUIs against purging. This prevents the currently selected items - from being purged even though they're off-screen. */ - - public function setPurgable(entities:Array, purgable:Boolean):void { - for each (var entity:Entity in entities) { - entity.locked=!purgable; - if ( entity is Way ) { - var way:Way=entity as Way; - if (paint.wayuis[way.id]) { paint.wayuis[way.id].purgable=purgable; } - for (var i:uint=0; i>>> REFACTOR: needs to do the equivalent of VectorLayer.blank() + } } - public function removeVectorLayer(layer:VectorLayer):void { - if (!layer) return; - layer.blank(); - vectorbg.removeChild(layer.paint); - delete vectorlayers[layer.name]; + public function findLayer(name:String):MapPaint { + for (var i:uint=0; iposition */ public var sublayerIndex:Object={}; + /** The url of the style in use */ + public var style:String = ''; + private const VERYBIG:Number=Math.pow(2,16); private static const NO_LAYER:int=-99999; // same as NodeUI @@ -40,23 +49,37 @@ package net.systemeD.halcyon { *

Each paint sprite has 4 child sprites (fill, casing, stroke, names). Each hit sprite has 2 child sprites (way hit tests, node hit tests).

*

Thus if layers range from -5 to +5, there will be 11 top level paint sprites followed by 11 top level hit sprites.

* - * @param map The map to be rendered. - * @param minlayer The lowest layer in that map that will be rendered. - * @param maxlayer The top layer in that map that will be rendered. + * @param map The Map this is attached to. (Required for finding out bounds and scale.) + * @param connection The Connection containing the data for this layer. + * @param minlayer The lowest OSM layer to display. + * @param maxlayer The highest OSM layer to display. * */ - public function MapPaint(map:Map,minlayer:int,maxlayer:int) { + public function MapPaint(map:Map, connection:Connection, styleurl:String, minlayer:int, maxlayer:int) { mouseEnabled=false; this.map=map; + this.connection=connection; this.minlayer=minlayer; this.maxlayer=maxlayer; sublayerIndex[1]=0; var s:Sprite, l:int; + // Set up stylesheet + setStyle(styleurl); + + // Listen for changes on this Connection + connection.addEventListener(Connection.NEW_WAY, newWayCreatedListener); + connection.addEventListener(Connection.NEW_POI, newPOICreatedListener); + connection.addEventListener(Connection.WAY_RENUMBERED, wayRenumberedListener); + connection.addEventListener(Connection.NODE_RENUMBERED, nodeRenumberedListener); + connection.addEventListener(Connection.NEW_MARKER, newMarkerCreatedListener); + // Add paint sprites for (l=minlayer; l<=maxlayer; l++) { // each layer (10 is +5, 0 is -5) s = getPaintSprite(); // | - s.addChild(getPaintSprite()); // | 0 fill + var q:Sprite = getPaintSprite(); // | 0 fill + q.addChild(getPaintSprite()); // | | sublayer + s.addChild(q); // | | s.addChild(getPaintSprite()); // | 1 casing var t:Sprite = getPaintSprite(); // | 2 stroke t.addChild(getPaintSprite()); // | | sublayer @@ -91,7 +114,7 @@ package net.systemeD.halcyon { return true; } - public function sublayer(layer:int,sublayer:Number):Sprite { + public function sublayer(layer:int,spritetype:uint,sublayer:Number):Sprite { var l:DisplayObject; var o:DisplayObject; var index:String, ix:Number; @@ -113,8 +136,8 @@ package net.systemeD.halcyon { // add sprites for (var i:int=minlayer; i<=maxlayer; i++) { l=getChildAt(i-minlayer); - o=(l as Sprite).getChildAt(2); - (o as Sprite).addChildAt(getPaintSprite(),lowestAbovePos); + o=(l as Sprite).getChildAt(0); (o as Sprite).addChildAt(getPaintSprite(),lowestAbovePos); // fillsprite + o=(l as Sprite).getChildAt(2); (o as Sprite).addChildAt(getPaintSprite(),lowestAbovePos); // strokesprite } // update index @@ -130,26 +153,23 @@ package net.systemeD.halcyon { } l=getChildAt(layer-minlayer); - o=(l as Sprite).getChildAt(2); + o=(l as Sprite).getChildAt(spritetype); return ((o as Sprite).getChildAt(sublayerIndex[sublayer]) as Sprite); } /** - * Update, and if necessary, create / remove UIs for the given objects. - * The object is effectively lists of objects split into inside/outside pairs, e.g. - * { waysInside: [], waysOutside: [] } where each is a array of entities either inside - * or outside this current view window. UIs for the entities on "inside" lists will be created if necessary. + * Update, and if necessary, create / remove UIs for the current viewport. * Flags control redrawing existing entities and removing UIs from entities no longer in view. * - * @param o The object containing all the relevant entites. * @param redraw If true, all UIs for entities on "inside" lists will be redrawn * @param remove If true, all UIs for entites on "outside" lists will be removed. The purgable flag on UIs can override this, for example for selected objects. * fixme? add smarter behaviour for way nodes - remove NodeUIs from way nodes off screen, create them for ones * that scroll onto screen (for highlights etc) */ - public function updateEntityUIs(o:Object, redraw:Boolean, remove:Boolean):void { + public function updateEntityUIs(redraw:Boolean, remove:Boolean):void { var way:Way, poi:Node, marker:Marker; + var o:Object = connection.getObjectsByBbox(map.edge_l,map.edge_r,map.edge_t,map.edge_b); for each (way in o.waysInside) { if (!wayuis[way.id]) { createWayUI(way); } @@ -313,6 +333,7 @@ package net.systemeD.halcyon { return s; } + /** Redraw all entities */ public function redraw():void { for each (var w:WayUI in wayuis) { w.recalculate(); w.invalidateStyleList(); w.redraw(); } /* sometimes (e.g. in Map.setStyle) Mappaint.redrawPOIs() is called immediately afterwards anyway. FIXME? */ @@ -320,18 +341,171 @@ package net.systemeD.halcyon { for each (var m:MarkerUI in markeruis) { m.invalidateStyleList(); m.redraw(); } } - /** Redraw nodes and markers */ + /** Redraw nodes and markers only */ public function redrawPOIs():void { for each (var p:NodeUI in nodeuis) { p.invalidateStyleList(); p.redraw(); } for each (var m:MarkerUI in markeruis) { m.invalidateStyleList(); m.redraw(); } } - public function findSource():VectorLayer { - var v:VectorLayer; - for each (v in map.vectorlayers) { - if (v.paint==this) { return v; } + /** Redraw a single entity if it exists */ + public function redrawEntity(e:Entity):Boolean { + if (e is Way && wayuis[e.id]) wayuis[e.id].redraw(); + else if (e is Node && nodeuis[e.id]) nodeuis[e.id].redraw(); + else if (e is Marker && markeruis[e.id]) markeruis[e.id].redraw(); + else return false; + return true; + } + + /** Switch to new MapCSS. */ + public function setStyle(url:String):void { + style = url; + ruleset=new RuleSet(map.MINSCALE,map.MAXSCALE,redraw,redrawPOIs); + ruleset.loadFromCSS(url); + } + + /** Does an entity belong to this layer? */ + public function sameConnection(entity:Entity):Boolean { + return entity.connection==this.connection; + } + + // ==================== Start of code moved from Map.as + + // Listeners for Connection events + + private function newWayCreatedListener(event:EntityEvent):void { + var way:Way = event.entity as Way; + if (!way.loaded || !way.within(map.edge_l, map.edge_r, map.edge_t, map.edge_b)) { return; } + createWayUI(way); + } + + private function newPOICreatedListener(event:EntityEvent):void { + var node:Node = event.entity as Node; + if (!node.within(map.edge_l, map.edge_r, map.edge_t, map.edge_b)) { return; } + createNodeUI(node); + } + + private function newMarkerCreatedListener(event:EntityEvent):void { + var marker:Marker = event.entity as Marker; + if (!marker.within(map.edge_l, map.edge_r, map.edge_t, map.edge_b)) { return; } + createMarkerUI(marker); + } + + private function wayRenumberedListener(event:EntityRenumberedEvent):void { + var way:Way = event.entity as Way; + renumberWayUI(way,event.oldID); + } + + private function nodeRenumberedListener(event:EntityRenumberedEvent):void { + var node:Node = event.entity as Node; + renumberNodeUI(node,event.oldID); + } + + /** Visually mark an entity as highlighted. */ + public function setHighlight(entity:Entity, settings:Object):void { + if ( entity is Way && wayuis[entity.id] ) { wayuis[entity.id].setHighlight(settings); } + else if ( entity is Node && nodeuis[entity.id]) { nodeuis[entity.id].setHighlight(settings); } + } + + public function setHighlightOnNodes(way:Way, settings:Object):void { + if (wayuis[way.id]) wayuis[way.id].setHighlightOnNodes(settings); + } + + public function protectWay(way:Way):void { + if (wayuis[way.id]) wayuis[way.id].protectSprites(); + } + + public function unprotectWay(way:Way):void { + if (wayuis[way.id]) wayuis[way.id].unprotectSprites(); + } + + public function limitWayDrawing(way:Way,except:Number=NaN,only:Number=NaN):void { + if (!wayuis[way.id]) return; + wayuis[way.id].drawExcept=except; + wayuis[way.id].drawOnly =only; + wayuis[way.id].redraw(); + } + + /** Protect Entities and EntityUIs against purging. This prevents the currently selected items + from being purged even though they're off-screen. */ + + public function setPurgable(entities:Array, purgable:Boolean):void { + for each (var entity:Entity in entities) { + entity.locked=!purgable; + if ( entity is Way ) { + var way:Way=entity as Way; + if (wayuis[way.id]) { wayuis[way.id].purgable=purgable; } + for (var i:uint=0; ilayer.paint.updateEntityUIs(layer.getObjectsByBbox(...)...); - */ - public function createNode(tags:Object,lat:Number,lon:Number):Node { - var node:Node = new Node(negativeID, 0, tags, true, lat, lon); - nodes[negativeID]=node; negativeID--; - return node; - } - - /** - * @param tags The tags for the new Way - * @param nodes An array of Node objects - */ - public function createWay(tags:Object,nodes:Array):Way { - var way:Way = new Way(negativeID, 0, tags, true, nodes.concat()); - ways[negativeID]=way; negativeID--; - return way; - } - - /** - * @param tags The tags for the new relation - * @param members An array of RelationMember objects - */ - public function createRelation(tags:Object,members:Array):Relation { - var relation:Relation = new Relation(negativeID, 0, tags, true, members.concat()); - relations[negativeID]=relation; negativeID--; - return relation; - } - - /** - * Create a new Marker on the VectorLayer. If you pass in an id it'll check first whether that - * marker has been created already, and won't duplicate it. - * - * @param tags The tags for the new Marker - * @param lat The latitude - * @param lon The longitude - * @param id Use this id for the marker, useful for when layer might be reloaded during panning - */ - public function createMarker(tags:Object,lat:Number,lon:Number,id:Number=NaN):Marker { - if (!id) { - id = negativeID; - negativeID--; - } - var marker:Marker = markers[id]; - if (marker == null) { - marker = new Marker(id, 0, tags, true, lat, lon); - markers[id]=marker; - } - return marker; - } - - public function registerPOI(node:Node):void { - if (pois.indexOf(node)<0) { pois.push(node); } - } - public function unregisterPOI(node:Node):void { - var index:uint = pois.indexOf(node); - if ( index >= 0 ) { pois.splice(index,1); } - } - - public function getObjectsByBbox(left:Number, right:Number, top:Number, bottom:Number):Object { - // ** FIXME: this is just copied-and-pasted from Connection.as, which really isn't very - // good practice. Is there a more elegant way of doing it? - var o:Object = { poisInside: [], poisOutside: [], waysInside: [], waysOutside: [], - markersInside: [], markersOutside: [] }; - - for each (var way:Way in ways) { - if (way.within(left,right,top,bottom)) { o.waysInside.push(way); } - else { o.waysOutside.push(way); } - } - for each (var poi:Node in pois) { - if (poi.within(left,right,top,bottom)) { o.poisInside.push(poi); } - else { o.poisOutside.push(poi); } - } - for each (var marker:Marker in markers) { - if (marker.within(left,right,top,bottom)) { o.markersInside.push(marker); } - else { o.markersOutside.push(marker); } - } - return o; - } - - /** - * Transfers an entity from the VectorLayer into the main layer - * @param entity The entity from the VectorLayer that you want to transfer. - * @param connection The Connection instance to transfer to (eg Connection.getConnection() ) - * - * @return either the newly created entity, or null - */ - public function pullThrough(entity:Entity,connection:Connection):Entity { - var i:uint=0; - var oldNode:Node, newNode:Node; - if (entity is Way) { - // copy way through to main layer - // ** shouldn't do this if the nodes are already in the main layer - // (or maybe we should just match on lat/long to avoid ways in background having nodes in foreground) - var oldWay:Way=Way(entity); - var newWay:Way=connection.createWay(oldWay.getTagsCopy(), [], MainUndoStack.getGlobalStack().addAction); - var nodemap:Object={}; - for (i=0; i= paint.map.edge_b && node.lat <= paint.map.edge_t && - node.lon >= paint.map.edge_l && node.lon <= paint.map.edge_r) { - paint.nodeuis[node.id].setHighlight(settings); // Triggers redraw if required - } - if (settings.selectedway || settings.hoverway) - nodehighlightsettings=settings; - else - nodehighlightsettings={}; + // Speed things up a bit by only setting the highlight if it's either: + // a) an "un-highlight" (so we don't leave mess behind when scrolling) + // b) currently onscreen + // Currently this means if you highlight an object then scroll, nodes will scroll + // into view that should be highlighted but aren't. + if (settings.hoverway==false || + settings.selectedway==false || + node.within(paint.map.edge_l, paint.map.edge_r, paint.map.edge_t, paint.map.edge_b)) { + paint.setHighlight(node,settings); // Triggers redraw if required } + if (settings.selectedway || settings.hoverway) + nodehighlightsettings=settings; + else + nodehighlightsettings={}; } } @@ -238,8 +231,10 @@ package net.systemeD.halcyon { // Copy tags object, and add states var tags:Object = entity.getTagsCopy(); setStateClass('area', Way(entity).isArea()); + setStateClass('background', paint.isBackground); setStateClass('tiger', (entity.isUneditedTiger() && Globals.vars.highlightTiger)); tags=applyStateClasses(tags); + if (entity.status) { tags['_status']=entity.status; } // Keep track of maximum stroke width for hitzone var maxwidth:Number=4; @@ -265,7 +260,7 @@ package net.systemeD.halcyon { indexEnd =Math.min(drawOnly+2,Way(entity).length); } - // Iterate through each sublayer, drawing any styles on that layer + // Iterate through each subpart, drawing any styles on that layer var drawn:Boolean; var multis:Array=entity.findParentRelationsOfType('multipolygon','outer'); var inners:Array=[]; @@ -273,9 +268,9 @@ package net.systemeD.halcyon { inners=inners.concat(m.findMembersByRole('inner',Way)); } - for each (var sublayer:Number in styleList.sublayers) { - if (styleList.shapeStyles[sublayer]) { - var s:ShapeStyle=styleList.shapeStyles[sublayer]; + for each (var subpart:String in styleList.subparts) { + if (styleList.shapeStyles[subpart]) { + var s:ShapeStyle=styleList.shapeStyles[subpart]; var stroke:Shape, fill:Shape, casing:Shape, roadname:Sprite; var x0:Number=paint.map.lon2coord(Way(entity).getNode(0).lon); var y0:Number=paint.map.latp2coord(Way(entity).getNode(0).latp); @@ -283,7 +278,7 @@ package net.systemeD.halcyon { // Stroke if (s.width) { - stroke=new Shape(); addToLayer(stroke,STROKESPRITE,sublayer); + stroke=new Shape(); addToLayer(stroke,STROKESPRITE,s.sublayer); stroke.graphics.moveTo(x0,y0); s.applyStrokeStyle(stroke.graphics); if (s.dashes && s.dashes.length>0) { @@ -296,7 +291,7 @@ package net.systemeD.halcyon { // Fill if ((!isNaN(s.fill_color) || s.fill_image) && entity.findParentRelationsOfType('multipolygon','inner').length==0 && isNaN(drawExcept)) { - fill=new Shape(); addToLayer(fill,FILLSPRITE); + fill=new Shape(); addToLayer(fill,FILLSPRITE,s.sublayer); fill.graphics.moveTo(x0,y0); if (s.fill_image) { new WayBitmapFiller(this,fill.graphics,s); } else { s.applyFill(fill.graphics); } @@ -317,8 +312,8 @@ package net.systemeD.halcyon { } } - if (styleList.textStyles[sublayer] && isNaN(drawExcept)) { - var t:TextStyle=styleList.textStyles[sublayer]; + if (styleList.textStyles[subpart] && isNaN(drawExcept)) { + var t:TextStyle=styleList.textStyles[subpart]; interactive||=t.interactive; roadname=new Sprite(); addToLayer(roadname,NAMESPRITE); nameformat = t.getTextFormat(); diff --git a/net/systemeD/halcyon/connection/AMFConnection.as b/net/systemeD/halcyon/connection/AMFConnection.as index 37d243d1..1c1e58b1 100644 --- a/net/systemeD/halcyon/connection/AMFConnection.as +++ b/net/systemeD/halcyon/connection/AMFConnection.as @@ -25,18 +25,18 @@ package net.systemeD.halcyon.connection { // ------------------------------------------------------------ // Constructor for new AMFConnection - public function AMFConnection() { + public function AMFConnection(name:String,api:String,policy:String,initparams:Object) { - if (Connection.policyURL!='') - Security.loadPolicyFile(Connection.policyURL); + super(name,api,policy,initparams); + if (policyURL!='') Security.loadPolicyFile(policyURL); readConnection=new NetConnection(); readConnection.objectEncoding = flash.net.ObjectEncoding.AMF0; - readConnection.connect(Connection.apiBaseURL+"amf/read"); + readConnection.connect(apiBaseURL+"amf/read"); writeConnection=new NetConnection(); writeConnection.objectEncoding = flash.net.ObjectEncoding.AMF0; - writeConnection.connect(Connection.apiBaseURL+"amf/write"); + writeConnection.connect(apiBaseURL+"amf/write"); } @@ -106,7 +106,7 @@ package net.systemeD.halcyon.connection { var lat:Number = Number(p[2]); var lon:Number = Number(p[1]); var tags:Object = p[3]; - node = new Node(id, version, tags, true, lat, lon); + node = new Node(this, id, version, tags, true, lat, lon); setNode(node,true); } registerPOI(node); @@ -151,7 +151,7 @@ package net.systemeD.halcyon.connection { var node:Node = getNode(nodeID); if ( node == null ) { var nodeTags:Object = p[3]; - node = new Node(nodeID, nodeVersion, nodeTags, true, lat, lon); + node = new Node(this, nodeID, nodeVersion, nodeTags, true, lat, lon); } else if (!node.loaded) { node.update(nodeVersion, nodeTags, true, false, lat, lon); } @@ -160,7 +160,7 @@ package net.systemeD.halcyon.connection { } if (way==null) { - way = new Way(id, version, tags, true, nodes); + way = new Way(this, id, version, tags, true, nodes); } else { way.update(version, tags, true, true, nodes); } @@ -198,21 +198,21 @@ package net.systemeD.halcyon.connection { switch (type) { case 'Node': e=getNode(memid); - if (e==null) { e=new Node(memid,0,{},false,0,0); setNode(Node(e),true); } + if (e==null) { e=new Node(this, memid,0,{},false,0,0); setNode(Node(e),true); } break; case 'Way': e=getWay(memid); - if (e==null) { e=new Way(memid,0,{},false,[]); setWay(Way(e),true); } + if (e==null) { e=new Way(this, memid,0,{},false,[]); setWay(Way(e),true); } break; case 'Relation': e=getRelation(memid); - if (e==null) { e=new Relation(memid,0,{},false,[]); setRelation(Relation(e),true); } + if (e==null) { e=new Relation(this, memid,0,{},false,[]); setRelation(Relation(e),true); } break; } members.push(new RelationMember(e,role)); } if (relation==null) { - relation = new Relation(id, version, tags, true, members); + relation = new Relation(this, id, version, tags, true, members); } else { relation.update(version,tags,true,false,members); } diff --git a/net/systemeD/halcyon/connection/Changeset.as b/net/systemeD/halcyon/connection/Changeset.as index 096651d0..bb1a2bf1 100644 --- a/net/systemeD/halcyon/connection/Changeset.as +++ b/net/systemeD/halcyon/connection/Changeset.as @@ -3,8 +3,8 @@ package net.systemeD.halcyon.connection { public class Changeset extends Entity { public static var entity_type:String = 'changeset'; - public function Changeset(id:Number, tags:Object) { - super(id, 0, tags, true, NaN, ''); + public function Changeset(connection:Connection, id:Number, tags:Object) { + super(connection, id, 0, tags, true, NaN, ''); } public override function toString():String { diff --git a/net/systemeD/halcyon/connection/Connection.as b/net/systemeD/halcyon/connection/Connection.as index 74514eb2..3e762ff5 100644 --- a/net/systemeD/halcyon/connection/Connection.as +++ b/net/systemeD/halcyon/connection/Connection.as @@ -7,48 +7,37 @@ package net.systemeD.halcyon.connection { import net.systemeD.halcyon.AttentionEvent; import net.systemeD.halcyon.MapEvent; import net.systemeD.halcyon.connection.actions.*; + import net.systemeD.halcyon.Globals; public class Connection extends EventDispatcher { - private static var connectionInstance:Connection = null; - - protected static var policyURL:String; - protected static var apiBaseURL:String; - protected static var params:Object; - - public static function getConnection(initparams:Object=null):Connection { - if ( connectionInstance == null ) { - - params = initparams == null ? new Object() : initparams; - policyURL = getParam("policy", "http://127.0.0.1:3000/api/crossdomain.xml"); - apiBaseURL = getParam("api", "http://127.0.0.1:3000/api/0.6/"); - var connectType:String = getParam("connection", "XML"); - - if ( connectType == "XML" ) - connectionInstance = new XMLConnection(); - else if ( connectType == "OSM" ) - connectionInstance = new OSMConnection(); - else - connectionInstance = new AMFConnection(); - } - return connectionInstance; - } + public var name:String; + public var statusFetcher:StatusFetcher; + protected var apiBaseURL:String; + protected var policyURL:String; + protected var params:Object; - public static function getParam(name:String, defaultValue:String):String { - return params[name] == null ? defaultValue : params[name]; + public function Connection(cname:String,api:String,policy:String,initparams:Object=null) { + initparams = (initparams!=null ? initparams:{}); + name=cname; + apiBaseURL=api; + policyURL=policy; + params=initparams; + } + + public function getParam(name:String, defaultValue:String):String { + if (params[name]) return params[name]; + if (Globals.vars.flashvars[name]) return Globals.vars.flashvars[name]; // REFACTOR - given the profusion of connections, should this be removed? + return defaultValue; } public function get apiBase():String { return apiBaseURL; } - public static function get serverName():String { + public function get serverName():String { return getParam("serverName", "Localhost"); } - - public static function getConnectionInstance():Connection { - return connectionInstance; - } public function getEnvironment(responder:Responder):void {} @@ -65,10 +54,12 @@ package net.systemeD.halcyon.connection { public static var NEW_WAY:String = "new_way"; public static var NEW_RELATION:String = "new_relation"; public static var NEW_POI:String = "new_poi"; + public static var NEW_MARKER:String = "new_marker"; public static var NODE_RENUMBERED:String = "node_renumbered"; public static var WAY_RENUMBERED:String = "way_renumbered"; public static var RELATION_RENUMBERED:String = "relation_renumbered"; - public static var TAG_CHANGED:String = "tag_change"; + public static var TAG_CHANGED:String = "tag_changed"; + public static var STATUS_CHANGED:String = "status_changed"; public static var NODE_MOVED:String = "node_moved"; public static var NODE_ALTERED:String = "node_altered"; public static var WAY_NODE_ADDED:String = "way_node_added"; @@ -91,6 +82,7 @@ package net.systemeD.halcyon.connection { private var nodes:Object = {}; private var ways:Object = {}; private var relations:Object = {}; + private var markers:Object = {}; private var pois:Array = []; private var changeset:Changeset = null; private var changesetUpdated:Number; @@ -200,6 +192,10 @@ package net.systemeD.halcyon.connection { return relations[id]; } + public function getMarker(id:Number):Marker { + return markers[id]; + } + protected function findEntity(type:String, id:*):Entity { var i:Number=Number(id); switch (type.toLowerCase()) { @@ -271,23 +267,39 @@ package net.systemeD.halcyon.connection { } public function createNode(tags:Object, lat:Number, lon:Number, performCreate:Function):Node { - var node:Node = new Node(nextNegative, 0, tags, true, lat, lon); + var node:Node = new Node(this, nextNegative, 0, tags, true, lat, lon); performCreate(new CreateEntityAction(node, setNode)); return node; } public function createWay(tags:Object, nodes:Array, performCreate:Function):Way { - var way:Way = new Way(nextNegative, 0, tags, true, nodes.concat()); + var way:Way = new Way(this, nextNegative, 0, tags, true, nodes.concat()); performCreate(new CreateEntityAction(way, setWay)); return way; } public function createRelation(tags:Object, members:Array, performCreate:Function):Relation { - var relation:Relation = new Relation(nextNegative, 0, tags, true, members.concat()); + var relation:Relation = new Relation(this, nextNegative, 0, tags, true, members.concat()); performCreate(new CreateEntityAction(relation, setRelation)); return relation; } + /** Create a new marker. This can't be done as part of a Composite Action. */ + // REFACTOR This needs renaming and/or refactoring to behave more similarly to n/w/r + public function createMarker(tags:Object,lat:Number,lon:Number,id:Number=NaN):Marker { + if (!id) { + id = negativeID; + negativeID--; + } + var marker:Marker = markers[id]; + if (marker == null) { + marker = new Marker(this, id, 0, tags, true, lat, lon); + markers[id]=marker; + sendEvent(new EntityEvent(NEW_MARKER, marker),false); + } + return marker; + } + public function getAllNodeIDs():Array { var list:Array = []; for each (var node:Node in nodes) @@ -309,6 +321,14 @@ package net.systemeD.halcyon.connection { return list; } + public function getAllLoadedEntities():Array { + var list:Array = []; var entity:Entity; + for each (entity in relations) { if (entity.loaded && !entity.deleted) list.push(entity); } + for each (entity in ways ) { if (entity.loaded && !entity.deleted) list.push(entity); } + for each (entity in nodes ) { if (entity.loaded && !entity.deleted) list.push(entity); } + return list; + } + /** Returns all available relations that match all of {k1: [v1,v2,...], k2: [v1...] ...} * where p1 is an array [v1, v2, v3...] */ public function getMatchingRelationIDs(match:Object):Array { @@ -328,7 +348,8 @@ package net.systemeD.halcyon.connection { } public function getObjectsByBbox(left:Number, right:Number, top:Number, bottom:Number):Object { - var o:Object = { poisInside: [], poisOutside: [], waysInside: [], waysOutside: [] }; + var o:Object = { poisInside: [], poisOutside: [], waysInside: [], waysOutside: [], + markersInside: [], markersOutside: [] }; for each (var way:Way in ways) { if (way.within(left,right,top,bottom)) { o.waysInside.push(way); } else { o.waysOutside.push(way); } @@ -337,6 +358,10 @@ package net.systemeD.halcyon.connection { if (poi.within(left,right,top,bottom)) { o.poisInside.push(poi); } else { o.poisOutside.push(poi); } } + for each (var marker:Marker in markers) { + if (marker.within(left,right,top,bottom)) { o.markersInside.push(marker); } + else { o.markersOutside.push(marker); } + } return o; } @@ -469,6 +494,13 @@ package net.systemeD.halcyon.connection { return []; } + public function identicalNode(node:Node):Node { + for each (var dupe:Node in nodePositions[node.lat+","+node.lon]) { + if (node.lat==dupe.lat && node.lon==dupe.lon && node.sameTags(dupe)) return dupe; + } + return null; + } + // Error-handling protected function throwConflictError(entity:Entity,serverVersion:uint,message:String):void { @@ -549,7 +581,7 @@ package net.systemeD.halcyon.connection { public function setAccessToken(key:String, secret:String):void {} public function createChangeset(tags:Object):void {} public function closeChangeset():void {} - public function uploadChanges():void {} + public function uploadChanges():* {} public function fetchUserTraces(refresh:Boolean=false):void {} public function fetchTrace(id:Number, callback:Function):void {} public function hasAccessToken():Boolean { return false; } diff --git a/net/systemeD/halcyon/connection/Entity.as b/net/systemeD/halcyon/connection/Entity.as index 6a24481f..8b4629bf 100644 --- a/net/systemeD/halcyon/connection/Entity.as +++ b/net/systemeD/halcyon/connection/Entity.as @@ -1,5 +1,6 @@ package net.systemeD.halcyon.connection { + import flash.events.Event; import flash.events.EventDispatcher; import flash.utils.Dictionary; @@ -8,6 +9,7 @@ package net.systemeD.halcyon.connection { /** An Entity is an object stored in the map database, and therefore uploaded and downloaded. This includes Nodes, Ways, * Relations but also Changesets etc. */ public class Entity extends EventDispatcher { + private var _connection:Connection; private var _id:Number; private var _version:uint; private var _uid:Number; @@ -16,13 +18,15 @@ package net.systemeD.halcyon.connection { private var modified:Boolean = false; private var _loaded:Boolean = true; private var parents:Dictionary = new Dictionary(); + public var status:String; /** Lock against purging when off-screen */ public var locked:Boolean = false; public var deleted:Boolean = false; /** Have all its parents (ie, relations that contain this object as a member, ways that contain this node) been loaded into memory */ public var parentsLoaded:Boolean = true; - public function Entity(id:Number, version:uint, tags:Object, loaded:Boolean, uid:Number, timestamp:String) { + public function Entity(connection:Connection, id:Number, version:uint, tags:Object, loaded:Boolean, uid:Number, timestamp:String) { + this._connection = connection; this._id = id; this._version = version; this._uid = uid; @@ -62,6 +66,11 @@ package net.systemeD.halcyon.connection { return _timestamp; } + /** Connection to which this entity belongs. */ + public function get connection():Connection { + return _connection; + } + /** Set a bunch of properties in one hit. Implicitly makes entity not deleted. */ public function updateEntityProperties(version:uint, tags:Object, loaded:Boolean, parentsLoaded:Boolean, uid:Number, timestamp:String):void { _version=version; this.tags=tags; _loaded=loaded; this.parentsLoaded=parentsLoaded; _uid = uid; _timestamp = timestamp; @@ -94,6 +103,16 @@ package net.systemeD.halcyon.connection { return false; } + /** Compare tags between two entities. */ + public function sameTags(entity:Entity):Boolean { + var o:Object=entity.getTagsHash(); + for (var k:String in tags) + if (!o[k] || o[k]!=tags[k]) return false; + for (k in o) + if (!tags[k] || tags[k]!=o[k]) return false; + return true; + } + /** Rough function to detect entities untouched since TIGER import. */ public function isUneditedTiger():Boolean { // todo: make this match the rules from the tiger edited map @@ -161,6 +180,14 @@ package net.systemeD.halcyon.connection { return copy; } + /** Change entity status. */ + public function setStatus(s:String):void { + if (s=='') s=null; + if (s==status) return; + status=s; + dispatchEvent(new EntityEvent(Connection.STATUS_CHANGED,this)); + } + // Clean/dirty methods /** Check if entity is modified since last markClean(). */ @@ -195,9 +222,9 @@ package net.systemeD.halcyon.connection { if (this is Node) { var n:Node = Node(this); if (isDeleted) { - Connection.getConnection().removeDupe(n); + connection.removeDupe(n); } else { - Connection.getConnection().addDupe(n); + connection.addDupe(n); } } } @@ -377,7 +404,7 @@ package net.systemeD.halcyon.connection { } - /** Basic description of Entity - should be overriden by subclass. */ + /** Basic description of Entity - should be overriden by subclass. */ public function getDescription():String { var basic:String=this.getType()+" "+_id; if (tags['ref'] && tags['name']) { return tags['ref']+' '+tags['name']+' ('+basic+')'; } @@ -398,31 +425,22 @@ package net.systemeD.halcyon.connection { /** Copy tags from another entity into this one, creating "key=value1; value2" pairs if necessary. * * @return Array of keys that require manual merging, in order to warn the user. */ - public function mergeTags(source: Entity, performAction:Function):Array { + public function mergeTags(source: Entity, performAction:Function):Boolean { var sourcetags:Object = source.getTagsHash(); - var problem_keys:Array=new Array(); + var conflict:Boolean = false; for (var k:String in sourcetags) { var v1:String = tags[k]; var v2:String = sourcetags[k]; if ( v1 && v1 != v2) { - // This can create broken tags (does anything support "highway=residential; tertiary"?). - // Probably better to do something like: - // highway=residential - // highway:tomerge=tertiary - setTag(k, v1+"; "+v2, performAction); - problem_keys.push(k); + conflict=true; } else { setTag(k, v2, performAction); } } - if (problem_keys.length > 0) - return problem_keys; - else - return null; + return conflict; } - } } diff --git a/net/systemeD/halcyon/connection/EntityCollection.as b/net/systemeD/halcyon/connection/EntityCollection.as index 6056cc6c..ad7f6b85 100644 --- a/net/systemeD/halcyon/connection/EntityCollection.as +++ b/net/systemeD/halcyon/connection/EntityCollection.as @@ -18,7 +18,10 @@ package net.systemeD.halcyon.connection { private var delayedEvents:Array = []; public function EntityCollection(entities:Array) { - super(-1, 0, {}, true, -1, ""); + var conn:Connection=entities[0].connection; + // ** FIXME: this really is a very nasty way of finding the connection + + super(conn, -1, 0, {}, true, -1, ""); _entities = entities; //To avoid firing on every contained entity, we wait some short time before firing the events diff --git a/net/systemeD/halcyon/connection/MainUndoStack.as b/net/systemeD/halcyon/connection/MainUndoStack.as index be75b6dc..38f87ff0 100644 --- a/net/systemeD/halcyon/connection/MainUndoStack.as +++ b/net/systemeD/halcyon/connection/MainUndoStack.as @@ -103,6 +103,12 @@ package net.systemeD.halcyon.connection { return false; } } + + public function removeLastIfAction(action:Class):void { + if (undoActions.length && undoActions[undoActions.length-1] is action) { + undoActions.pop(); + } + } [Bindable(event="new_undo_item")] public function getUndoDescription():String { @@ -111,6 +117,13 @@ package net.systemeD.halcyon.connection { return null; } + [Bindable(event="new_redo_item")] + public function getRedoDescription():String { + if (redoActions.length==0) return null; + if (redoActions[redoActions.length-1].name) return redoActions[redoActions.length-1].name; + return null; + } + /** * Takes the action most recently undone, does it, and adds it to the undo stack */ diff --git a/net/systemeD/halcyon/connection/Marker.as b/net/systemeD/halcyon/connection/Marker.as index 0180bfb4..e39c5c9f 100644 --- a/net/systemeD/halcyon/connection/Marker.as +++ b/net/systemeD/halcyon/connection/Marker.as @@ -10,8 +10,8 @@ package net.systemeD.halcyon.connection { private var _latproj:Number; private var _lon:Number; - public function Marker(id:Number, version:uint, tags:Object, loaded:Boolean, lat:Number, lon:Number) { - super(id, version, tags, loaded, 0, null); + public function Marker(connection:Connection, id:Number, version:uint, tags:Object, loaded:Boolean, lat:Number, lon:Number) { + super(connection, id, version, tags, loaded, 0, null); this._lat = lat; this._latproj = lat2latp(lat); this._lon = lon; diff --git a/net/systemeD/halcyon/connection/Node.as b/net/systemeD/halcyon/connection/Node.as index e185d3f4..f3bc8d99 100644 --- a/net/systemeD/halcyon/connection/Node.as +++ b/net/systemeD/halcyon/connection/Node.as @@ -7,8 +7,8 @@ package net.systemeD.halcyon.connection { private var _latproj:Number; private var _lon:Number; - public function Node(id:Number, version:uint, tags:Object, loaded:Boolean, lat:Number, lon:Number, uid:Number = NaN, timestamp:String = null) { - super(id, version, tags, loaded, uid, timestamp); + public function Node(connection:Connection, id:Number, version:uint, tags:Object, loaded:Boolean, lat:Number, lon:Number, uid:Number = NaN, timestamp:String = null) { + super(connection, id, version, tags, loaded, uid, timestamp); this._lat = lat; this._latproj = lat2latp(lat); this._lon = lon; @@ -31,7 +31,6 @@ package net.systemeD.halcyon.connection { } private function setLatLonImmediate(lat:Number, lon:Number):void { - var connection:Connection = Connection.getConnection(); connection.removeDupe(this); this._lat = lat; this._latproj = lat2latp(lat); @@ -89,7 +88,6 @@ package net.systemeD.halcyon.connection { */ public function join(ways:Array, performAction:Function):void { if (this.isDupe() || ways.length > 0) { - var connection:Connection = Connection.getConnection(); var nodes:Array = connection.getNodesAtPosition(lat,lon); // filter the nodes array to remove any occurances of this. // Pass "this" as thisObject to get "this" into the callback function @@ -111,7 +109,6 @@ package net.systemeD.halcyon.connection { } public function isDupe():Boolean { - var connection:Connection = Connection.getConnection(); if (connection.getNode(this.id) == this // node could be part of a vector layer && connection.nodesAtPosition(lat, lon) > 1) { return true; diff --git a/net/systemeD/halcyon/connection/OSMConnection.as b/net/systemeD/halcyon/connection/OSMConnection.as index 18f12944..6e08f517 100644 --- a/net/systemeD/halcyon/connection/OSMConnection.as +++ b/net/systemeD/halcyon/connection/OSMConnection.as @@ -44,15 +44,15 @@ package net.systemeD.halcyon.connection { private static const FILENAME:RegExp=/([\-\d\.]+)_([\-\d\.]+)_([\-\d\.]+)_([\-\d\.]+)\./i; - public function OSMConnection() { + public function OSMConnection(name:String,api:String,policy:String,initparams:Object) { - if (Connection.policyURL!='') - Security.loadPolicyFile(Connection.policyURL); + super(name,api,policy,initparams); + if (policyURL!='') Security.loadPolicyFile(policyURL); - tileResolution = Number(Connection.getParam("tile_resolution", "0.2")); + tileResolution = Number(getParam("tile_resolution", "0.2")); var o:Object = new Object(); - var files:String = Connection.getParam("files",""); + var files:String = getParam("files",""); if (files=="") { filemode=TILED; } else { @@ -97,7 +97,7 @@ package net.systemeD.halcyon.connection { private function loadFile(box:Array):void { bboxes[box]=LOADING; - var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"/"+box[0]+"_"+box[1]+"_"+box[2]+"_"+box[3]+".osm"); + var mapRequest:URLRequest = new URLRequest(apiBaseURL+"/"+box[0]+"_"+box[1]+"_"+box[2]+"_"+box[3]+".osm"); var mapLoader:ExtendedURLLoader = new ExtendedURLLoader(); mapLoader.info['bbox']=box; mapLoader.addEventListener(Event.COMPLETE, markMapLoaded); diff --git a/net/systemeD/halcyon/connection/Relation.as b/net/systemeD/halcyon/connection/Relation.as index 795dd781..56808d41 100644 --- a/net/systemeD/halcyon/connection/Relation.as +++ b/net/systemeD/halcyon/connection/Relation.as @@ -6,8 +6,8 @@ package net.systemeD.halcyon.connection { private var members:Array; public static var entity_type:String = 'relation'; - public function Relation(id:Number, version:uint, tags:Object, loaded:Boolean, members:Array, uid:Number = NaN, timestamp:String = null) { - super(id, version, tags, loaded, uid, timestamp); + public function Relation(connection:Connection, id:Number, version:uint, tags:Object, loaded:Boolean, members:Array, uid:Number = NaN, timestamp:String = null) { + super(connection, id, version, tags, loaded, uid, timestamp); this.members = members; for each (var member:RelationMember in members) member.entity.addParent(this); diff --git a/net/systemeD/halcyon/connection/StatusFetcher.as b/net/systemeD/halcyon/connection/StatusFetcher.as new file mode 100644 index 00000000..25cf8ca4 --- /dev/null +++ b/net/systemeD/halcyon/connection/StatusFetcher.as @@ -0,0 +1,85 @@ +package net.systemeD.halcyon.connection { + + import flash.net.*; + import flash.events.*; + import net.systemeD.halcyon.AttentionEvent; + + public class StatusFetcher { + + /* Class to fetch the status for newly loaded objects. + At present this is rather specialised to WTFE (wtfe.gryph.de). + */ + + public var _url:String; + public var conn:Connection; + + private static var STATUSES:Array=["no","partial","unsure",'']; + + public function StatusFetcher(url:String, connection:Connection) { + _url=url; + conn=connection; + } + + public function fetch(entities:Array):void { + if (entities.length==0) return; + // Create URL request + var vars:URLVariables = new URLVariables(); + vars.nodes=''; + vars.ways=''; + vars.relations=''; + for each (var entity:Entity in entities) { + if (entity is Node) vars.nodes+=entity.id+","; + else if (entity is Way) vars.ways+=entity.id+","; + else if (entity is Relation) vars.relations+=entity.id+","; + } + if (vars.ways.substr(vars.ways.length-1,1)==',') vars.ways=vars.ways.substr(0,vars.ways.length-1); + if (vars.nodes.substr(vars.nodes.length-1,1)==',') vars.nodes=vars.nodes.substr(0,vars.nodes.length-1); + if (vars.relations.substr(vars.relations.length-1,1)==',') vars.relations=vars.relations.substr(0,vars.relations.length-1); + var request:URLRequest = new URLRequest(_url); + request.data = vars; + request.method = "POST"; + + // Make request + var loader:URLLoader = new URLLoader(); + loader.addEventListener(Event.COMPLETE, loadedFetch); + loader.addEventListener(IOErrorEvent.IO_ERROR, errorOnFetchLoad); + loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, fetchLoadStatus); + loader.load(request); + } + + private function loadedFetch(event:Event):void { + var xml:XML=new XML(URLLoader(event.target).data); + var entity:Entity, status:String; + + for each (var exml:XML in xml.*) { + switch (exml.name().localName) { + case "way": entity=conn.getWay(exml.@id); break; + case "relation":entity=conn.getRelation(exml.@id); break; + case "node": entity=conn.getNode(exml.@id); break; + } + + // **** Specific WTFE-parsing code starts here + // FIXME: should be generalised + // if all users are "yes" or "auto", status is 'ok' (green) + // if first user is "no", status is 'no' (red) + // if any other users are no, status is 'partial' (softer red) + // otherwise, status is 'unsure' (yellow) + var s:uint=3; // ok + for each (var user:XML in exml.user) { + if (user.@decision=='no' && user.@version=='first') { s=0; } // no from v1 + else if (user.@decision=='no') { s=Math.min(s,1); } // no from later version + else if (user.@decision=='undecided' || user.@decision=='anonymous') { s=Math.min(s,2); } // unsure + } + status=STATUSES[s]; + // **** Specific WTFE-parsing code ends here + entity.setStatus(status); + } + } + + private function errorOnFetchLoad(event:Event):void { + conn.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Couldn't load status information")); + } + private function fetchLoadStatus(event:HTTPStatusEvent):void { } + + } +} diff --git a/net/systemeD/halcyon/connection/Trace.as b/net/systemeD/halcyon/connection/Trace.as index 5c4402fd..22a0b64e 100644 --- a/net/systemeD/halcyon/connection/Trace.as +++ b/net/systemeD/halcyon/connection/Trace.as @@ -8,7 +8,7 @@ package net.systemeD.halcyon.connection { import net.systemeD.halcyon.connection.*; import net.systemeD.halcyon.Map; import net.systemeD.halcyon.Globals; - import net.systemeD.halcyon.VectorLayer; + import net.systemeD.halcyon.MapPaint; /** * Implements trace objects loaded from the OSM API. @@ -16,23 +16,31 @@ package net.systemeD.halcyon.connection { * loading GPX files (and other formats) from arbitrary urls. */ public class Trace extends EventDispatcher { - private var _id:Number; - private var _description:String; + private var _id:Number; // id of the trace, as reported by the server + private var _description:String; // description, as reported by the server private var tags:Array = []; // N.B. trace tags are NOT k/v pairs - private var _isLoaded:Boolean; - private var _filename:String; - private var _traceData:String; + private var _isLoaded:Boolean; // Flag for when the data has been downloaded and parsed + private var _filename:String; // The original name of the file, as reported by the server + private var _traceData:String; // the trace data, saved as a string private var map:Map; - private var _layer:VectorLayer; + private var _layer:MapPaint; + private var masterConnection:XMLConnection; // The authenticated connection + private var _connection:Connection; // The one we store our fake nodes/ways in. private var simplify:Boolean = false; private static const STYLESHEET:String="stylesheets/gpx.css"; - public function Trace() { - map = Globals.vars.root; + /** Create a new trace. + * @param masterConnection The authenticated connection to communicate with the server + */ + public function Trace(masterConnection:XMLConnection) { + this.masterConnection = masterConnection; + map = Globals.vars.root; // REFACTOR this prevents traces being added to arbitrary maps } - /* Create a new trace, from the XML description given by the user/traces call */ + /** Create a new trace, from the XML description given by the user/traces call. + * This only creates the object itself, the actual trace contents (trkseg etc) are + * lazily downloaded later. */ public function fromXML(xml:XML):Trace { _id = Number(xml.@id); _filename = xml.@name; @@ -61,7 +69,7 @@ package net.systemeD.halcyon.connection { private function fetchFromServer():void { // todo - needs proper error handling - Connection.getConnectionInstance().fetchTrace(id, saveTraceData); + masterConnection.fetchTrace(id, saveTraceData); dispatchEvent(new Event("loading_data")); } @@ -70,10 +78,18 @@ package net.systemeD.halcyon.connection { dispatchEvent(new Event("loaded_data")); } - private function get layer():VectorLayer { + private function get connection():Connection { + if (!_connection) { + // create a new connection so that the ids don't impact the main layer. + _connection = new Connection(filename, null, null, null); + } + return _connection + } + + private function get layer():MapPaint { if (!_layer) { - _layer=new VectorLayer(filename,map,STYLESHEET); - map.addVectorLayer(_layer); + // create a new layer for every trace, so they can be turned on/off individually + _layer = map.addLayer(connection, STYLESHEET); } return _layer; } @@ -101,6 +117,7 @@ package net.systemeD.halcyon.connection { private function process():void { var file:XML = new XML(_traceData); + var action:CompositeUndoableAction = new CompositeUndoableAction("add trace objects"); for each (var ns:Namespace in file.namespaceDeclarations()) { if (ns.uri.match(/^http:\/\/www\.topografix\.com\/GPX\/1\/[01]$/)) { default xml namespace = ns; @@ -111,10 +128,10 @@ package net.systemeD.halcyon.connection { var way:Way; var nodestring:Array = []; for each (var trkpt:XML in trkseg.trkpt) { - nodestring.push(layer.createNode({}, trkpt.@lat, trkpt.@lon)); + nodestring.push(connection.createNode({}, trkpt.@lat, trkpt.@lon, action.push)); } if (nodestring.length > 0) { - way = layer.createWay({}, nodestring); + way = connection.createWay({}, nodestring, action.push); //if (simplify) { Simplify.simplify(way, paint.map, false); } } } @@ -124,12 +141,13 @@ package net.systemeD.halcyon.connection { for each (var tag:XML in wpt.children()) { tags[tag.name().localName]=tag.toString(); } - var node:Node = layer.createNode(tags, wpt.@lat, wpt.@lon); - layer.registerPOI(node); + var node:Node = connection.createNode(tags, wpt.@lat, wpt.@lon, action.push); + connection.registerPOI(node); } + action.doAction(); /* just do it, don't add to undo stack */ default xml namespace = new Namespace(""); - layer.paint.updateEntityUIs(layer.getObjectsByBbox(map.edge_l,map.edge_r,map.edge_t,map.edge_b), true, false); + layer.updateEntityUIs(true, false); } } } diff --git a/net/systemeD/halcyon/connection/UndoableEntityAction.as b/net/systemeD/halcyon/connection/UndoableEntityAction.as index b72c58b7..d6b784f8 100644 --- a/net/systemeD/halcyon/connection/UndoableEntityAction.as +++ b/net/systemeD/halcyon/connection/UndoableEntityAction.as @@ -36,7 +36,7 @@ package net.systemeD.halcyon.connection { } if ( !connectionWasDirty ) { - Connection.getConnectionInstance().markDirty(); + entity.connection.markDirty(); } } @@ -52,7 +52,7 @@ package net.systemeD.halcyon.connection { } if ( !connectionWasDirty ) { - Connection.getConnectionInstance().markClean(); + entity.connection.markClean(); } } @@ -62,7 +62,7 @@ package net.systemeD.halcyon.connection { */ private function init():void { wasDirty = entity.isDirty; - connectionWasDirty = Connection.getConnectionInstance().isDirty; + connectionWasDirty = entity.connection.isDirty; initialised = true; } diff --git a/net/systemeD/halcyon/connection/Way.as b/net/systemeD/halcyon/connection/Way.as index 87ed4e5d..0f97958b 100644 --- a/net/systemeD/halcyon/connection/Way.as +++ b/net/systemeD/halcyon/connection/Way.as @@ -11,8 +11,8 @@ package net.systemeD.halcyon.connection { private var edge_b:Number; public static var entity_type:String = 'way'; - public function Way(id:Number, version:uint, tags:Object, loaded:Boolean, nodes:Array, uid:Number = NaN, timestamp:String = null) { - super(id, version, tags, loaded, uid, timestamp); + public function Way(connection:Connection, id:Number, version:uint, tags:Object, loaded:Boolean, nodes:Array, uid:Number = NaN, timestamp:String = null) { + super(connection, id, version, tags, loaded, uid, timestamp); this.nodes = nodes; for each (var node:Node in nodes) { node.addParent(this); } calculateBbox(); @@ -51,7 +51,7 @@ package net.systemeD.halcyon.connection { (edge_b>top && edge_b>top ) || deleted) { return false; } return true; } - + public function getNode(index:uint):Node { return nodes[index]; } @@ -94,12 +94,12 @@ package net.systemeD.halcyon.connection { } public function appendNode(node:Node, performAction:Function):uint { - if (node!=getLastNode()) performAction(new AddNodeToWayAction(this, node, nodes, -1)); + insertNode(nodes.length, node, performAction); return nodes.length + 1; } public function prependNode(node:Node, performAction:Function):uint { - if (node!=getFirstNode()) performAction(new AddNodeToWayAction(this, node, nodes, 0)); + insertNode(0, node, performAction); return nodes.length + 1; } @@ -291,5 +291,51 @@ package net.systemeD.halcyon.connection { } return null; } + + public function intersects(left:Number,right:Number,top:Number,bottom:Number):Boolean { + // simple test first: are any nodes contained? + for (var i:uint=0; i0) { + top_intersection = (m*l + c); + bottom_intersection = (m*r + c); + } else { + top_intersection = (m*r + c); + bottom_intersection = (m*l + c); + } + + if (y0toptrianglepoint ? top_intersection : toptrianglepoint; + var botoverlap:Number = bottom_intersectionb))); + } + + } } diff --git a/net/systemeD/halcyon/connection/XMLBaseConnection.as b/net/systemeD/halcyon/connection/XMLBaseConnection.as index 4910bb50..c94a8572 100644 --- a/net/systemeD/halcyon/connection/XMLBaseConnection.as +++ b/net/systemeD/halcyon/connection/XMLBaseConnection.as @@ -1,171 +1,181 @@ package net.systemeD.halcyon.connection { - import flash.events.*; + import flash.events.*; import flash.system.Security; import flash.net.*; - import org.iotashan.oauth.*; + import org.iotashan.oauth.*; - import net.systemeD.halcyon.MapEvent; + import net.systemeD.halcyon.MapEvent; - /** - * XMLBaseConnection is the common code between connecting to an OSM server - * (i.e. XMLConnection) and connecting to a standalone .osm file (i.e. OSMConnection) - * and so mainly concerns itself with /map -call-ish related matters - */ + /** + * XMLBaseConnection is the common code between connecting to an OSM server + * (i.e. XMLConnection) and connecting to a standalone .osm file (i.e. OSMConnection) + * and so mainly concerns itself with /map -call-ish related matters + */ public class XMLBaseConnection extends Connection { - public function XMLBaseConnection() { + public function XMLBaseConnection(name:String,api:String,policy:String,initparams:Object) { + super(name,api,policy,initparams); } - protected function loadedMap(event:Event):void { - var map:XML = new XML(URLLoader(event.target).data); - + protected function loadedMap(event:Event):void { + var map:XML = new XML(URLLoader(event.target).data); + if (map.localName=="osmError") { - dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't load the map: " + map.message } )); + dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't load the map: " + map.message } )); } else { - var id:Number; - var version:uint; - var uid:Number; - var timestamp:String; - var tags:Object; - var node:Node, newNode:Node; - var unusedNodes:Object={}; + var id:Number; + var version:uint; + var uid:Number; + var timestamp:String; + var tags:Object; + var node:Node, newNode:Node; + var unusedNodes:Object={}; + var createdEntities:Array=[]; - var minlon:Number, maxlon:Number, minlat:Number, maxlat:Number; - var singleEntityRequest:Boolean=true; - if (map.bounds.@minlon.length()) { - minlon=map.bounds.@minlon; - maxlon=map.bounds.@maxlon; - minlat=map.bounds.@minlat; - maxlat=map.bounds.@maxlat; - singleEntityRequest=false; - } + var minlon:Number, maxlon:Number, minlat:Number, maxlat:Number; + var singleEntityRequest:Boolean=true; + if (map.bounds.@minlon.length()) { + minlon=map.bounds.@minlon; + maxlon=map.bounds.@maxlon; + minlat=map.bounds.@minlat; + maxlat=map.bounds.@maxlat; + singleEntityRequest=false; + } - for each(var relData:XML in map.relation) { - id = Number(relData.@id); - version = uint(relData.@version); - uid = Number(relData.@uid); - timestamp = relData.@timestamp; - - var rel:Relation = getRelation(id); - if ( rel == null || !rel.loaded || singleEntityRequest ) { - tags = parseTags(relData.tag); - var members:Array = []; - for each(var memberXML:XML in relData.member) { - var type:String = memberXML.@type.toLowerCase(); - var role:String = memberXML.@role; - var memberID:Number = Number(memberXML.@ref); - var member:Entity = null; - if ( type == "node" ) { - member = getNode(memberID); - if ( member == null ) { - member = new Node(memberID,0,{},false,0,0); - setNode(Node(member),true); - } else if (member.isDeleted()) { - member.setDeletedState(false); - } - } else if ( type == "way" ) { - member = getWay(memberID); - if (member == null) { - member = new Way(memberID,0,{},false,[]); - setWay(Way(member),true); - } - } else if ( type == "relation" ) { - member = getRelation(memberID); - if (member == null) { - member = new Relation(memberID,0,{},false,[]); - setRelation(Relation(member),true); - } - } - - if ( member != null ) - members.push(new RelationMember(member, role)); - } - - if ( rel == null ) - setRelation(new Relation(id, version, tags, true, members, uid, timestamp), false); - else { - rel.update(version, tags, true, false, members, uid, timestamp); - sendEvent(new EntityEvent(NEW_RELATION, rel), false); - } - } - } + for each(var relData:XML in map.relation) { + id = Number(relData.@id); + version = uint(relData.@version); + uid = Number(relData.@uid); + timestamp = relData.@timestamp; + + var rel:Relation = getRelation(id); + if ( rel == null || !rel.loaded || singleEntityRequest ) { + tags = parseTags(relData.tag); + var members:Array = []; + for each(var memberXML:XML in relData.member) { + var type:String = memberXML.@type.toLowerCase(); + var role:String = memberXML.@role; + var memberID:Number = Number(memberXML.@ref); + var member:Entity = null; + if ( type == "node" ) { + member = getNode(memberID); + if ( member == null ) { + member = new Node(this,memberID,0,{},false,0,0); + setNode(Node(member),true); + } else if (member.isDeleted()) { + member.setDeletedState(false); + } + } else if ( type == "way" ) { + member = getWay(memberID); + if (member == null) { + member = new Way(this,memberID,0,{},false,[]); + setWay(Way(member),true); + } + } else if ( type == "relation" ) { + member = getRelation(memberID); + if (member == null) { + member = new Relation(this,memberID,0,{},false,[]); + setRelation(Relation(member),true); + } + } + + if ( member != null ) + members.push(new RelationMember(member, role)); + } + + if ( rel == null ) { + rel=new Relation(this, id, version, tags, true, members, uid, timestamp); + setRelation(rel, false); + createdEntities.push(rel); + } else { + rel.update(version, tags, true, false, members, uid, timestamp); + sendEvent(new EntityEvent(NEW_RELATION, rel), false); + } + } + } - for each(var nodeData:XML in map.node) { - id = Number(nodeData.@id); - node = getNode(id); - newNode = new Node(id, - uint(nodeData.@version), - parseTags(nodeData.tag), - true, - Number(nodeData.@lat), - Number(nodeData.@lon), - Number(nodeData.@uid), - nodeData.@timestamp); + for each(var nodeData:XML in map.node) { + id = Number(nodeData.@id); + node = getNode(id); + newNode = new Node(this, + id, + uint(nodeData.@version), + parseTags(nodeData.tag), + true, + Number(nodeData.@lat), + Number(nodeData.@lon), + Number(nodeData.@uid), + nodeData.@timestamp); - if ( singleEntityRequest ) { - // it's a revert request, so create/update the node - setOrUpdateNode(newNode, true); - } else if ( node == null || !node.loaded) { - // the node didn't exist before, so create/update it - newNode.parentsLoaded=newNode.within(minlon,maxlon,minlat,maxlat); - setOrUpdateNode(newNode, true); - } else { - // the node's already in memory, but store it in case one of the new ways needs it - if (newNode.within(minlon,maxlon,minlat,maxlat)) newNode.parentsLoaded=true; - unusedNodes[id]=newNode; - } - } - - for each(var data:XML in map.way) { - id = Number(data.@id); - version = uint(data.@version); - uid = Number(data.@uid); - timestamp = data.@timestamp; + if ( singleEntityRequest ) { + // it's a revert request, so create/update the node + setOrUpdateNode(newNode, true); + } else if ( node == null || !node.loaded) { + // the node didn't exist before, so create/update it + newNode.parentsLoaded=newNode.within(minlon,maxlon,minlat,maxlat); + setOrUpdateNode(newNode, true); + createdEntities.push(newNode); + } else { + // the node's already in memory, but store it in case one of the new ways needs it + if (newNode.within(minlon,maxlon,minlat,maxlat)) newNode.parentsLoaded=true; + unusedNodes[id]=newNode; + } + } + + for each(var data:XML in map.way) { + id = Number(data.@id); + version = uint(data.@version); + uid = Number(data.@uid); + timestamp = data.@timestamp; - var way:Way = getWay(id); - if ( way == null || !way.loaded || singleEntityRequest) { - var nodes:Array = []; - for each(var nd:XML in data.nd) { - var nodeid:Number=Number(nd.@ref) - if (getNode(nodeid).isDeleted() && unusedNodes[nodeid]) { - setOrUpdateNode(unusedNodes[nodeid], true); - } - nodes.push(getNode(nodeid)); - } - tags = parseTags(data.tag); - if ( way == null ) { - setWay(new Way(id, version, tags, true, nodes, uid, timestamp),false); - } else { - waycount++; - way.update(version, tags, true, true, nodes, uid, timestamp); - sendEvent(new EntityEvent(NEW_WAY, way), false); - } - } - } - - markBboxLoaded(minlon,maxlon,maxlat,minlat); - registerPOINodes(); - } + var way:Way = getWay(id); + if ( way == null || !way.loaded || singleEntityRequest) { + var nodelist:Array = []; + for each(var nd:XML in data.nd) { + var nodeid:Number=Number(nd.@ref) + if (getNode(nodeid).isDeleted() && unusedNodes[nodeid]) { + setOrUpdateNode(unusedNodes[nodeid], true); + } + nodelist.push(getNode(nodeid)); + } + tags = parseTags(data.tag); + if ( way == null ) { + way=new Way(this, id, version, tags, true, nodelist, uid, timestamp) + setWay(way,false); + createdEntities.push(way); + } else { + waycount++; + way.update(version, tags, true, true, nodelist, uid, timestamp); + sendEvent(new EntityEvent(NEW_WAY, way), false); + } + } + } + + markBboxLoaded(minlon,maxlon,maxlat,minlat); + registerPOINodes(); + } - dispatchEvent(new Event(LOAD_COMPLETED)); - } - - protected function registerPOINodes():void { - for each (var nodeID:Number in getAllNodeIDs()) { - var node:Node = getNode(nodeID); - if (!node.hasParentWays) - registerPOI(node); - } - } + dispatchEvent(new Event(LOAD_COMPLETED)); - private function parseTags(tagElements:XMLList):Object { - var tags:Object = {}; - for each (var tagEl:XML in tagElements) - tags[tagEl.@k] = tagEl.@v; - return tags; - } + if (statusFetcher) statusFetcher.fetch(createdEntities); + } + + protected function registerPOINodes():void { + for each (var nodeID:Number in getAllNodeIDs()) { + var node:Node = getNode(nodeID); + if (!node.hasParentWays) + registerPOI(node); + } + } + + private function parseTags(tagElements:XMLList):Object { + var tags:Object = {}; + for each (var tagEl:XML in tagElements) + tags[tagEl.@k] = tagEl.@v; + return tags; + } } } diff --git a/net/systemeD/halcyon/connection/XMLConnection.as b/net/systemeD/halcyon/connection/XMLConnection.as index 7fe45ec7..3a4c5ef3 100644 --- a/net/systemeD/halcyon/connection/XMLConnection.as +++ b/net/systemeD/halcyon/connection/XMLConnection.as @@ -16,14 +16,13 @@ package net.systemeD.halcyon.connection { */ public class XMLConnection extends XMLBaseConnection { - public function XMLConnection() { + public function XMLConnection(name:String,api:String,policy:String,initparams:Object) { - if (Connection.policyURL!='') - Security.loadPolicyFile(Connection.policyURL); - var oauthPolicy:String = Connection.getParam("oauth_policy", ""); - if ( oauthPolicy != "" ) { - Security.loadPolicyFile(oauthPolicy); - } + super(name,api,policy,initparams); + if (policyURL != "") Security.loadPolicyFile(policyURL); + + var oauthPolicy:String = getParam("oauth_policy", ""); + if (oauthPolicy != "") Security.loadPolicyFile(oauthPolicy); } override public function loadBbox(left:Number,right:Number, @@ -40,14 +39,14 @@ package net.systemeD.halcyon.connection { var mapVars:URLVariables = new URLVariables(); mapVars.bbox= left+","+bottom+","+right+","+top; - var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"map"); + var mapRequest:URLRequest = new URLRequest(apiBaseURL+"map"); mapRequest.data = mapVars; sendLoadRequest(mapRequest); } override public function loadEntityByID(type:String, id:Number):void { - var url:String=Connection.apiBaseURL + type + "/" + id; + var url:String=apiBaseURL + type + "/" + id; if (type=='way') url+="/full"; sendLoadRequest(new URLRequest(url)); } @@ -132,7 +131,7 @@ package net.systemeD.halcyon.connection { changesetXML.changeset.appendChild(tagXML); } - sendOAuthPut(Connection.apiBaseURL+"changeset/create", + sendOAuthPut(apiBaseURL+"changeset/create", changesetXML, changesetCreateComplete, changesetCreateError, recordStatus); } @@ -142,7 +141,7 @@ package net.systemeD.halcyon.connection { var id:Number = Number(URLLoader(event.target).data); // which means we now have a new changeset! - setActiveChangeset(new Changeset(id, lastUploadedChangesetTags)); + setActiveChangeset(new Changeset(this, id, lastUploadedChangesetTags)); } private function changesetCreateError(event:IOErrorEvent):void { @@ -153,7 +152,7 @@ package net.systemeD.halcyon.connection { var cs:Changeset = getActiveChangeset(); if (!cs) return; - sendOAuthPut(Connection.apiBaseURL+"changeset/"+cs.id+"/close", + sendOAuthPut(apiBaseURL+"changeset/"+cs.id+"/close", null, changesetCloseComplete, changesetCloseError, recordStatus); closeActiveChangeset(); @@ -199,7 +198,10 @@ package net.systemeD.halcyon.connection { loader.load(urlReq); } - override public function uploadChanges():void { + /** Create XML changeset and send it to the server. Returns the XML string for use in the 'Show data' button. + (We don't mind what's returned as long as it implements .toString() ) */ + + override public function uploadChanges():* { var changeset:Changeset = getActiveChangeset(); var upload:XML = upload.appendChild(addCreated(changeset, getAllNodeIDs, getNode, serialiseNode)); @@ -217,7 +219,7 @@ package net.systemeD.halcyon.connection { // now actually upload them // make an OAuth query - var url:String = Connection.apiBaseURL+"changeset/" + changeset.id + "/upload"; + var url:String = apiBaseURL+"changeset/" + changeset.id + "/upload"; // build the actual request var serv:HTTPService=new HTTPService(); @@ -232,7 +234,8 @@ package net.systemeD.halcyon.connection { serv.addEventListener(FaultEvent.FAULT, diffUploadIOError); serv.send(upload); - dispatchEvent(new Event(SAVE_STARTED)); + dispatchEvent(new Event(SAVE_STARTED)); + return upload; } private function diffUploadComplete(event:ResultEvent):void { @@ -431,8 +434,7 @@ package net.systemeD.halcyon.connection { if (traces_loaded && !refresh) { dispatchEvent(new Event(TRACES_LOADED)); } else { - sendOAuthGet(Connection.apiBaseURL+"user/gpx_files", - tracesLoadComplete, errorOnMapLoad, mapLoadStatus); //needs error handlers + sendOAuthGet(apiBaseURL+"user/gpx_files", tracesLoadComplete, errorOnMapLoad, mapLoadStatus); //needs error handlers dispatchEvent(new Event(LOAD_STARTED)); //specific to map or reusable? } } @@ -441,7 +443,7 @@ package net.systemeD.halcyon.connection { clearTraces(); var files:XML = new XML(URLLoader(event.target).data); for each(var traceData:XML in files.gpx_file) { - var t:Trace = new Trace().fromXML(traceData); + var t:Trace = new Trace(this).fromXML(traceData); addTrace(t); } traces_loaded = true; @@ -450,7 +452,7 @@ package net.systemeD.halcyon.connection { } override public function fetchTrace(id:Number, callback:Function):void { - sendOAuthGet(Connection.apiBaseURL+"gpx/"+id+"/data.xml", + sendOAuthGet(apiBaseURL+"gpx/"+id+"/data.xml", function(e:Event):void { dispatchEvent(new Event(LOAD_COMPLETED)); callback(e); diff --git a/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as b/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as index 7e291e93..185bf559 100644 --- a/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as +++ b/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as @@ -6,6 +6,7 @@ package net.systemeD.halcyon.connection.actions { private var node:Node; private var nodeList:Array; private var index:int; + private var firstNode:Node; public function AddNodeToWayAction(way:Way, node:Node, nodeList:Array, index:int) { super(way, "Add node "+node.id+" to"); @@ -16,8 +17,17 @@ package net.systemeD.halcyon.connection.actions { public override function doAction():uint { var way:Way = entity as Way; - if ( index == -1 ) - index = nodeList.length; + + // undelete way if it was deleted before (only happens on redo) + if (way.deleted) { + way.setDeletedState(false); + if (!firstNode.hasParentWays) firstNode.connection.unregisterPOI(firstNode); + firstNode.addParent(way); + way.connection.dispatchEvent(new EntityEvent(Connection.NEW_WAY, way)); + } + + // add node + if ( index == -1 ) index = nodeList.length; node.addParent(way); nodeList.splice(index, 0, node); markDirty(); @@ -29,13 +39,32 @@ package net.systemeD.halcyon.connection.actions { public override function undoAction():uint { var way:Way = entity as Way; + + // ** FIXME: if the user undoes adding the 2nd node, then we delete the way and create a POI from the + // one remaining node (see below). _However_, when we delete the way, we also need to remove + // it from any relations... and to do that, this needs to be a CompositeUndoableAction. + // Which it isn't (because we want all the markDirty/markClean stuff). So, for now, we'll + // simply refuse to undo adding the 2nd node if the way is in any relations. (This should + // be a vanishingly small case anyway, because usually the AddMemberToRelationAction will + // have been undone already.) + if (way.length==2 && way.parentRelations.length) return FAIL; + + // remove node var removed:Array=nodeList.splice(index, 1); if (nodeList.indexOf(removed[0])==-1) { removed[0].removeParent(way); } markClean(); way.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, removed[0], way, index)); - return SUCCESS; + // delete way if it's now 1-length, and convert the one remaining node to a POI + if (way.length==1) { + way.setDeletedState(true); + way.dispatchEvent(new EntityEvent(Connection.WAY_DELETED, way)); + firstNode=way.getNode(0); + firstNode.removeParent(way); + if (!firstNode.hasParentWays) firstNode.connection.registerPOI(firstNode); + MainUndoStack.getGlobalStack().removeLastIfAction(BeginWayAction); + } + return SUCCESS; } } } - diff --git a/net/systemeD/halcyon/connection/actions/CreatePOIAction.as b/net/systemeD/halcyon/connection/actions/CreatePOIAction.as index 99d0b58d..42a35fbf 100644 --- a/net/systemeD/halcyon/connection/actions/CreatePOIAction.as +++ b/net/systemeD/halcyon/connection/actions/CreatePOIAction.as @@ -10,9 +10,11 @@ package net.systemeD.halcyon.connection.actions { private var tags:Object; private var lat:Number; private var lon:Number; + private var connection:Connection; - public function CreatePOIAction(tags:Object, lat:Number, lon:Number) { + public function CreatePOIAction(connection:Connection, tags:Object, lat:Number, lon:Number) { super("Create POI"); + this.connection = connection; this.tags = tags; this.lat = lat; this.lon = lon; @@ -20,17 +22,17 @@ package net.systemeD.halcyon.connection.actions { public override function doAction():uint { if (newNode == null) { - newNode = Connection.getConnection().createNode(tags,lat,lon,push); + newNode = connection.createNode(tags,lat,lon,push); } super.doAction(); - Connection.getConnection().registerPOI(newNode); + connection.registerPOI(newNode); return SUCCESS; } public override function undoAction():uint { super.undoAction(); - Connection.getConnection().unregisterPOI(newNode); + connection.unregisterPOI(newNode); return SUCCESS; } diff --git a/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as b/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as index b5bebfa4..beda1c7f 100644 --- a/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as +++ b/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as @@ -49,7 +49,7 @@ package net.systemeD.halcyon.connection.actions { } else { markClean(); } - Connection.getConnection().dispatchEvent(new EntityEvent(Connection.NEW_NODE, entity)); + node.connection.dispatchEvent(new EntityEvent(Connection.NEW_NODE, entity)); if ( effects != null ) effects.undoAction(); return SUCCESS; diff --git a/net/systemeD/halcyon/connection/actions/DeleteWayAction.as b/net/systemeD/halcyon/connection/actions/DeleteWayAction.as index 3ed4e856..f2fd1bc6 100644 --- a/net/systemeD/halcyon/connection/actions/DeleteWayAction.as +++ b/net/systemeD/halcyon/connection/actions/DeleteWayAction.as @@ -56,8 +56,8 @@ package net.systemeD.halcyon.connection.actions { } else { markClean(); } - Connection.getConnection().dispatchEvent(new EntityEvent(Connection.NEW_WAY, way)); - effects.undoAction(); + entity.connection.dispatchEvent(new EntityEvent(Connection.NEW_WAY, way)); + if (effects) effects.undoAction(); for each(var node:Node in oldNodeList) { nodeList.push(node); node.addParent(way); diff --git a/net/systemeD/halcyon/connection/actions/MergeNodesAction.as b/net/systemeD/halcyon/connection/actions/MergeNodesAction.as index 60b7d4c1..c037b3b5 100644 --- a/net/systemeD/halcyon/connection/actions/MergeNodesAction.as +++ b/net/systemeD/halcyon/connection/actions/MergeNodesAction.as @@ -1,53 +1,56 @@ -package net.systemeD.halcyon.connection.actions -{ - import net.systemeD.halcyon.connection.*; - - - public class MergeNodesAction extends CompositeUndoableAction { - // Node2's tags are merged into node1, then node2 is deleted. - private var node1:Node; - private var node2:Node; - static public var lastProblemTags:Array; - - public function MergeNodesAction(destnode:Node, sourcenode:Node) { - super("Merge nodes "+destnode.id+" "+sourcenode.id); - this.node1 = destnode; - this.node2 = sourcenode; - lastProblemTags=null; - } - - public override function doAction():uint { - - super.clearActions(); - node1.suspend(); - -// mergeRelations(); TODO - lastProblemTags= node1.mergeTags(node2,push); // TODO use to warn user - node2.replaceWith(node1, push); - node2.remove(push); - - super.doAction(); - node1.resume(); - - return SUCCESS; - } - - public override function undoAction():uint { - node1.suspend(); - super.undoAction(); - node1.resume(); - - return SUCCESS; - } - - public function mergeRelations():void { - for each (var r:Relation in node2.parentRelations) { - // ** needs to copy roles as well - if (r.findEntityMemberIndex(node1)==-1) { - r.appendMember(new RelationMember(node1, ''), push); - } - } - } - } - +package net.systemeD.halcyon.connection.actions +{ + import net.systemeD.halcyon.connection.*; + + + public class MergeNodesAction extends CompositeUndoableAction { + // Node2's tags are merged into node1, then node2 is deleted. + private var node1:Node; + private var node2:Node; + // ** FIXME: not at all elegant having these stuffed in static variables; we should probably use events instead + static public var lastTagsMerged:Boolean; + static public var lastRelationsMerged:Boolean; + + public function MergeNodesAction(destnode:Node, sourcenode:Node) { + super("Merge nodes "+destnode.id+" "+sourcenode.id); + this.node1 = destnode; + this.node2 = sourcenode; + lastTagsMerged=false; + lastRelationsMerged=false; + } + + public override function doAction():uint { + + super.clearActions(); + node1.suspend(); + + lastTagsMerged=node1.mergeTags(node2,push); + node2.replaceWith(node1, push); + node2.remove(push); + + super.doAction(); + node1.resume(); + + return SUCCESS; + } + + public override function undoAction():uint { + node1.suspend(); + super.undoAction(); + node1.resume(); + + return SUCCESS; + } + + public function mergeRelations():void { + for each (var r:Relation in node2.parentRelations) { + // ** needs to copy roles as well + if (r.findEntityMemberIndex(node1)==-1) { + r.appendMember(new RelationMember(node1, ''), push); + lastRelationsMerged=true; + } + } + } + } + } \ No newline at end of file diff --git a/net/systemeD/halcyon/connection/actions/MergeWaysAction.as b/net/systemeD/halcyon/connection/actions/MergeWaysAction.as index 33d5c803..4a5c3b8d 100644 --- a/net/systemeD/halcyon/connection/actions/MergeWaysAction.as +++ b/net/systemeD/halcyon/connection/actions/MergeWaysAction.as @@ -7,7 +7,9 @@ package net.systemeD.halcyon.connection.actions { private var way2:Way; private var toPos:uint; private var fromPos:uint; - static public var lastProblemTags:Array; + // ** FIXME: not at all elegant having these stuffed in static variables; we should probably use events instead + static public var lastTagsMerged:Boolean; + static public var lastRelationsMerged:Boolean; public function MergeWaysAction(way1:Way, way2:Way, toPos:uint, fromPos:uint) { super("Merge ways "+way1.id+" "+way2.id); @@ -15,7 +17,8 @@ package net.systemeD.halcyon.connection.actions { this.way2 = way2; this.toPos = toPos; this.fromPos = fromPos; - lastProblemTags=null; + lastTagsMerged = false; + lastRelationsMerged = false; } public override function doAction():uint { @@ -26,7 +29,7 @@ package net.systemeD.halcyon.connection.actions { way1.suspend(); mergeRelations(); - lastProblemTags = way1.mergeTags(way2,push); + lastTagsMerged = way1.mergeTags(way2,push); mergeNodes(); way2.remove(push); @@ -49,6 +52,7 @@ package net.systemeD.halcyon.connection.actions { // ** needs to copy roles as well if (r.findEntityMemberIndex(way1)==-1) { r.appendMember(new RelationMember(way1, ''), push); + lastRelationsMerged=true; } } } diff --git a/net/systemeD/halcyon/connection/actions/RemoveNodeByIndexAction.as b/net/systemeD/halcyon/connection/actions/RemoveNodeByIndexAction.as index 2e623aec..9f2303da 100644 --- a/net/systemeD/halcyon/connection/actions/RemoveNodeByIndexAction.as +++ b/net/systemeD/halcyon/connection/actions/RemoveNodeByIndexAction.as @@ -18,26 +18,35 @@ package net.systemeD.halcyon.connection.actions { this.fireEvent = fireEvent; } - public override function doAction():uint { - removed = nodeList.splice(index, 1); - if (nodeList.indexOf(removed[0])==-1) { removed[0].removeParent(way); } - way.deleted = nodeList.length == 0; - markDirty(); - if (fireEvent) { - entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, removed[0], way, index)); - } - return SUCCESS; - } - - public override function undoAction():uint { - nodeList.splice(index, 0, removed[0]); - removed[0].addParent(way); - way.deleted = nodeList.length == 0; - markClean(); - if (fireEvent) { - entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, removed[0], way, index)); - } - return SUCCESS; + public override function doAction():uint { + var preceding:Node=(index>1) ? nodeList[index-1] : null; + var node:Node=nodeList[index]; + removed=[]; + + while (nodeList[index]==node || (nodeList[index]==preceding && preceding!=null)) { + var removedNode:Node=nodeList.splice(index, 1)[0]; + removed.push(removedNode); + if (nodeList.indexOf(removedNode)==-1) { removedNode.removeParent(way); } + if (fireEvent) { + entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, removedNode, way, index)); + } + } + way.deleted = nodeList.length == 0; + markDirty(); + return SUCCESS; + } + + public override function undoAction():uint { + for (var i:int=removed.length-1; i>=0; i--) { + nodeList.splice(index, 0, removed[i]); + removed[i].addParent(way); + if (fireEvent) { + entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, removed[i], way, index)); + } + } + way.deleted = nodeList.length == 0; + markClean(); + return SUCCESS; } } } \ No newline at end of file diff --git a/net/systemeD/halcyon/connection/actions/RemoveNodeFromWayAction.as b/net/systemeD/halcyon/connection/actions/RemoveNodeFromWayAction.as index 760866ac..cd3007b6 100644 --- a/net/systemeD/halcyon/connection/actions/RemoveNodeFromWayAction.as +++ b/net/systemeD/halcyon/connection/actions/RemoveNodeFromWayAction.as @@ -15,11 +15,19 @@ package net.systemeD.halcyon.connection.actions { public override function doAction():uint { nodeRemovedFrom = []; - var i:int; + var i:int, node2:Node; while ((i=nodeList.indexOf(node))>-1) { + // remove the node from the way nodeList.splice(i,1); - nodeRemovedFrom.push(i); - entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, node, Way(entity), i)); + nodeRemovedFrom.push([node,i]); + entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, node, Way(entity), i)); + + // remove any repeated nodes that have occurred as a result (i.e. removing B from ABA) + while (i>0 && nodeList[i-1]==nodeList[i]) { + node2=nodeList.splice(i,1)[0]; + nodeRemovedFrom.push([node2,i]); + entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_REMOVED, node2, Way(entity), i)); + } } if ( nodeRemovedFrom.length > 0 ) { @@ -36,9 +44,11 @@ package net.systemeD.halcyon.connection.actions { node.addParent(entity); for (var i:int = nodeRemovedFrom.length - 1; i >= 0; i--) { - var index:int = nodeRemovedFrom[i]; - nodeList.splice(index, 0, node); - entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, node, Way(entity), index)); + var removal:Array = nodeRemovedFrom[i]; + var reinstate:Node = removal[0]; + var index:int = removal[1]; + nodeList.splice(index, 0, reinstate); + entity.dispatchEvent(new WayNodeEvent(Connection.WAY_NODE_ADDED, reinstate, Way(entity), index)); } entity.deleted = nodeList.length == 0; diff --git a/net/systemeD/halcyon/connection/actions/SetTagAction.as b/net/systemeD/halcyon/connection/actions/SetTagAction.as index 9f1b90ce..c8b3bc61 100644 --- a/net/systemeD/halcyon/connection/actions/SetTagAction.as +++ b/net/systemeD/halcyon/connection/actions/SetTagAction.as @@ -17,8 +17,8 @@ package net.systemeD.halcyon.connection.actions { public override function doAction():uint { var tags:Object = entity.getTagsHash(); oldValue = tags[key]; - if ( oldValue != value || key == '' ) { - if ( value == null || value == "" || key == '' ) + if ( value == null || key == '' || oldValue != value ) { + if ( value == null || key == '' || value == '' ) delete tags[key]; else tags[key] = value; diff --git a/net/systemeD/halcyon/connection/actions/SplitWayAction.as b/net/systemeD/halcyon/connection/actions/SplitWayAction.as index b79efbdf..d88ddaee 100644 --- a/net/systemeD/halcyon/connection/actions/SplitWayAction.as +++ b/net/systemeD/halcyon/connection/actions/SplitWayAction.as @@ -16,7 +16,7 @@ package net.systemeD.halcyon.connection.actions { public override function doAction():uint { if (newWay==null) { - newWay = Connection.getConnection().createWay( + newWay = selectedWay.connection.createWay( selectedWay.getTagsCopy(), selectedWay.sliceNodes(nodeIndex,selectedWay.length), push); diff --git a/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as b/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as index a9e14e84..f6abba57 100644 --- a/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as +++ b/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as @@ -25,7 +25,7 @@ package net.systemeD.halcyon.connection.actions { way.dispatchEvent(new EntityEvent(Connection.WAY_REORDERED, way)); // no longer a junction, so force redraw continue; } else { - var newNode:Node = Connection.getConnection().createNode(node.getTagsCopy(), node.lat, node.lon, push); + var newNode:Node = way.connection.createNode(node.getTagsCopy(), node.lat, node.lon, push); for (var i:int = 0; i < way.length; i++) { if(way.getNode(i) == node) { way.removeNodeByIndex(i, push); diff --git a/net/systemeD/halcyon/styleparser/RuleChain.as b/net/systemeD/halcyon/styleparser/RuleChain.as new file mode 100644 index 00000000..61bca340 --- /dev/null +++ b/net/systemeD/halcyon/styleparser/RuleChain.as @@ -0,0 +1,69 @@ +package net.systemeD.halcyon.styleparser { + + import net.systemeD.halcyon.connection.*; + + /** A descendant list of MapCSS selectors (Rules). + + For example, + relation[type=route] way[highway=primary] + ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ + first Rule second Rule + |------------|---------| + | + one RuleChain + + */ + + public class RuleChain { + public var rules:Array=[]; // should eventually become a Vector of Rules + public var subpart:String='default'; // subpart name, as in way[highway=primary]::centreline + + // Test a ruleChain + // - run a set of tests in the chain + // works backwards from at position "pos" in array, or -1 for the last + // separate tags object is required in case they've been dynamically retagged + // - if they fail, return false + // - if they succeed, and it's the last in the chain, return happily + // - if they succeed, and there's more in the chain, rerun this for each parent until success + + public function test(pos:int, obj:Entity, tags:Object, zoom:uint):Boolean { + if (length==0) { return false; } + if (pos==-1) { pos=rules.length-1; } + + var r:Rule=rules[pos]; + if (!r.test(obj, tags, zoom)) { return false; } + if (pos==0) { return true; } + + var o:Array=obj.parentObjects; + for each (var p:Entity in o) { + if (test(pos-1, p, p.getTagsHash(), zoom)) { return true; } + } + return false; + } + + public function get length():int { + return rules.length; + } + + public function setSubpart(s:String):void { + subpart = s=='' ? 'default' : s; + } + + // --------------------------------------------------------------------------------------------- + // Methods to add properties (used by parsers such as MapCSS) + + public function addRule(e:String=''):void { + rules.push(new Rule(e)); + } + + public function addConditionToLast(c:Condition):void { + rules[rules.length-1].conditions.push(c); + } + + public function addZoomToLast(z1:uint,z2:uint):void { + rules[rules.length-1].minZoom=z1; + rules[rules.length-1].maxZoom=z2; + } + + } +} diff --git a/net/systemeD/halcyon/styleparser/RuleSet.as b/net/systemeD/halcyon/styleparser/RuleSet.as index 33c20b9c..1c9f6be8 100644 --- a/net/systemeD/halcyon/styleparser/RuleSet.as +++ b/net/systemeD/halcyon/styleparser/RuleSet.as @@ -43,6 +43,7 @@ package net.systemeD.halcyon.styleparser { private static const CONDITION:RegExp =/^ \[(.+?)\] \s* /sx; private static const OBJECT:RegExp =/^ (\w+) \s* /sx; private static const DECLARATION:RegExp =/^ \{(.+?)\} \s* /sx; + private static const SUBPART:RegExp =/^ ::(\w+) \s* /sx; private static const UNKNOWN:RegExp =/^ (\S+) \s* /sx; private static const ZOOM_MINMAX:RegExp =/^ (\d+)\-(\d+) $/sx; @@ -74,6 +75,7 @@ package net.systemeD.halcyon.styleparser { private static const oCONDITION:uint=4; private static const oOBJECT:uint=5; private static const oDECLARATION:uint=6; + private static const oSUBPART:uint=7; private static const DASH:RegExp=/\-/g; private static const COLOR:RegExp=/color$/; @@ -287,7 +289,7 @@ package net.systemeD.halcyon.styleparser { else if (style is ShieldStyle && ShieldStyle(style).shield_image) { filename=ShieldStyle(style).shield_image; } else { continue; } if (filename=='square' || filename=='circle') { continue; } - + iconsToLoad++; var request:DebugURLRequest=new DebugURLRequest(filename); var loader:ExtendedURLLoader=new ExtendedURLLoader(); @@ -366,7 +368,7 @@ package net.systemeD.halcyon.styleparser { if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); } css=css.replace(CLASS,''); - sc.addCondition(new Condition('set',o[1])); + sc.currentChain.addConditionToLast(new Condition('set',o[1])); previous=oCONDITION; // Not class - !.motorway, !.builtup, !:hover @@ -374,30 +376,31 @@ package net.systemeD.halcyon.styleparser { if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); } css=css.replace(NOT_CLASS,''); - sc.addCondition(new Condition('unset',o[1])); + sc.currentChain.addConditionToLast(new Condition('unset',o[1])); previous=oCONDITION; // Zoom } else if ((o=ZOOM.exec(css))) { - if (previous!=oOBJECT && previous!=oCONDITION) { sc.newObject(); } + if (previous!=oOBJECT && previous!=oCONDITION) { sc.currentChain.addRule(); } css=css.replace(ZOOM,''); var z:Array=parseZoom(o[1]); - sc.addZoom(z[0],z[1]); + sc.currentChain.addZoomToLast(z[0],z[1]); + sc.zoomSpecific=true; previous=oZOOM; // Grouping - just a comma } else if ((o=GROUP.exec(css))) { css=css.replace(GROUP,''); - sc.newGroup(); + sc.newRuleChain(); previous=oGROUP; // Condition - [highway=primary] } else if ((o=CONDITION.exec(css))) { if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); } - if (previous!=oOBJECT && previous!=oZOOM && previous!=oCONDITION) { sc.newObject(); } + if (previous!=oOBJECT && previous!=oZOOM && previous!=oCONDITION) { sc.currentChain.addRule(); } css=css.replace(CONDITION,''); - sc.addCondition(parseCondition(o[1]) as Condition); + sc.currentChain.addConditionToLast(parseCondition(o[1]) as Condition); previous=oCONDITION; // Object - way, node, relation @@ -405,9 +408,16 @@ package net.systemeD.halcyon.styleparser { if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); } css=css.replace(OBJECT,''); - sc.newObject(o[1]); + sc.currentChain.addRule(o[1]); previous=oOBJECT; + // Subpart - ::centreline + } else if ((o=SUBPART.exec(css))) { + if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); } + css=css.replace(SUBPART,''); + sc.currentChain.setSubpart(o[1]); + previous=oSUBPART; + // Declaration - {...} } else if ((o=DECLARATION.exec(css))) { css=css.replace(DECLARATION,''); diff --git a/net/systemeD/halcyon/styleparser/ShapeStyle.as b/net/systemeD/halcyon/styleparser/ShapeStyle.as index 03411fc0..1ec99967 100644 --- a/net/systemeD/halcyon/styleparser/ShapeStyle.as +++ b/net/systemeD/halcyon/styleparser/ShapeStyle.as @@ -45,7 +45,7 @@ package net.systemeD.halcyon.styleparser { } public function applyCasingStyle(g:Graphics):void { - g.lineStyle(casing_width, + g.lineStyle(width + casing_width, casing_color ? casing_color : 0, casing_opacity ? casing_opacity : 1, false, "normal", diff --git a/net/systemeD/halcyon/styleparser/StyleChooser.as b/net/systemeD/halcyon/styleparser/StyleChooser.as index 05b71b88..c2d29809 100644 --- a/net/systemeD/halcyon/styleparser/StyleChooser.as +++ b/net/systemeD/halcyon/styleparser/StyleChooser.as @@ -10,25 +10,35 @@ package net.systemeD.halcyon.styleparser { Its ruleChains property is an array of all the selectors, which would traditionally be comma-separated. For example: h1, h2, h3 em - is three ruleChains. + is three RuleChains. - Each ruleChain is itself an array of nested selectors. So the above + Each RuleChain is itself an array of nested selectors. So the above example would roughly be encoded as: [[h1],[h2],[h3,em]] ^^ ^^ ^^ ^^ each of these is a Rule - + ^^^^ ^^^^ ^^^^^^^ each of these is a RuleChain + The styles property is an array of all the style objects to be drawn if any of the ruleChains evaluate to true. */ - public var ruleChains:Array=[[]]; // array of array of Rules + public var ruleChains:Array; // array of RuleChains (each one an array of Rules) public var styles:Array=[]; // array of ShapeStyle/ShieldStyle/TextStyle/PointStyle public var zoomSpecific:Boolean=false; // are any of the rules zoom-specific? - private var rcpos:uint=0; + private var rcpos:uint; private var stylepos:uint=0; + public function StyleChooser():void { + ruleChains=[new RuleChain()]; + rcpos=0; + } + + public function get currentChain():RuleChain { + return ruleChains[rcpos]; + } + // Update the current StyleList from this StyleChooser public function updateStyles(obj:Entity, tags:Object, sl:StyleList, imageWidths:Object, zoom:uint):void { @@ -36,112 +46,68 @@ package net.systemeD.halcyon.styleparser { // Are any of the ruleChains fulfilled? var w:Number; - var fulfilled:Boolean=false; - for each (var c:Array in ruleChains) { - if (testChain(c,-1,obj,tags,zoom)) { - fulfilled=true; break; - } - } - if (!fulfilled) { return; } - - // Update StyleList - for each (var r:Style in styles) { - var a:*; - if (r is ShapeStyle) { - a=sl.shapeStyles; - if (ShapeStyle(r).width>sl.maxwidth && !r.evals['width']) { sl.maxwidth=ShapeStyle(r).width; } - } else if (r is ShieldStyle) { - a=sl.shieldStyles; - } else if (r is TextStyle) { - a=sl.textStyles; - } else if (r is PointStyle) { - a=sl.pointStyles; - w=0; - if (PointStyle(r).icon_width && !PointStyle(r).evals['icon_width']) { - w=PointStyle(r).icon_width; - } else if (PointStyle(r).icon_image && imageWidths[PointStyle(r).icon_image]) { - w=imageWidths[PointStyle(r).icon_image]; - } - if (w>sl.maxwidth) { sl.maxwidth=w; } - } else if (r is InstructionStyle) { - if (InstructionStyle(r).breaker) { return; } - if (InstructionStyle(r).set_tags) { - for (var k:String in InstructionStyle(r).set_tags) { tags[k]=InstructionStyle(r).set_tags[k]; } - } - continue; - } - if (r.drawn) { tags[':drawn']='yes'; } - tags['_width']=sl.maxwidth; + for each (var c:RuleChain in ruleChains) { + if (c.test(-1,obj,tags,zoom)) { + sl.addSubpart(c.subpart); + + // Update StyleList + for each (var r:Style in styles) { + var a:Object; + if (r is ShapeStyle) { + a=sl.shapeStyles; + if (ShapeStyle(r).width>sl.maxwidth && !r.evals['width']) { sl.maxwidth=ShapeStyle(r).width; } + } else if (r is ShieldStyle) { + a=sl.shieldStyles; + } else if (r is TextStyle) { + a=sl.textStyles; + } else if (r is PointStyle) { + a=sl.pointStyles; + w=0; + if (PointStyle(r).icon_width && !PointStyle(r).evals['icon_width']) { + w=PointStyle(r).icon_width; + } else if (PointStyle(r).icon_image && imageWidths[PointStyle(r).icon_image]) { + w=imageWidths[PointStyle(r).icon_image]; + } + if (w>sl.maxwidth) { sl.maxwidth=w; } + } else if (r is InstructionStyle) { + if (InstructionStyle(r).breaker) { return; } + if (InstructionStyle(r).set_tags) { + for (var k:String in InstructionStyle(r).set_tags) { tags[k]=InstructionStyle(r).set_tags[k]; } + } + continue; + } + if (r.drawn) { tags[':drawn']='yes'; } + tags['_width']=sl.maxwidth; - r.runEvals(tags); - sl.addSublayer(r.sublayer); - if (a[r.sublayer]) { - // If there's already a style on this sublayer, then merge them - // (making a deep copy if necessary to avoid altering the root style) - if (!a[r.sublayer].merged) { a[r.sublayer]=a[r.sublayer].deepCopy(); } - a[r.sublayer].mergeWith(r); - } else { - // Otherwise, just assign it - a[r.sublayer]=r; + r.runEvals(tags); + if (a[c.subpart]) { + // If there's already a style on this sublayer, then merge them + // (making a deep copy if necessary to avoid altering the root style) + if (!a[c.subpart].merged) { a[c.subpart]=a[c.subpart].deepCopy(); } + a[c.subpart].mergeWith(r); + } else { + // Otherwise, just assign it + a[c.subpart]=r; + } + } } } } - - - // Test a ruleChain - // - run a set of tests in the chain - // works backwards from at position "pos" in array, or -1 for the last - // separate tags object is required in case they've been dynamically retagged - // - if they fail, return false - // - if they succeed, and it's the last in the chain, return happily - // - if they succeed, and there's more in the chain, rerun this for each parent until success - - private function testChain(chain:Array,pos:int,obj:Entity,tags:Object,zoom:uint):Boolean { - if (pos==-1) { pos=chain.length-1; } - - var r:Rule=chain[pos]; - if (!r.test(obj, tags, zoom)) { return false; } - if (pos==0) { return true; } - - var o:Array=obj.parentObjects; - for each (var p:Entity in o) { - if (testChain(chain, pos-1, p, p.getTagsHash(), zoom )) { return true; } - } - return false; - } // --------------------------------------------------------------------------------------------- // Methods to add properties (used by parsers such as MapCSS) - // newGroup <- starts a new ruleChain in this.ruleChains - public function newGroup():void { + // newRuleChain <- starts a new ruleChain in this.ruleChains + public function newRuleChain():void { if (ruleChains[rcpos].length>0) { - ruleChains[++rcpos]=[]; + ruleChains[++rcpos]=new RuleChain(); } } - // newObject <- adds into the current ruleChain (starting a new Rule) - public function newObject(e:String=''):void { - ruleChains[rcpos].push(new Rule(e)); - } - - // addZoom <- adds into the current ruleChain (existing Rule) - public function addZoom(z1:uint,z2:uint):void { - ruleChains[rcpos][ruleChains[rcpos].length-1].minZoom=z1; - ruleChains[rcpos][ruleChains[rcpos].length-1].maxZoom=z2; - zoomSpecific=true; - } - - // addCondition <- adds into the current ruleChain (existing Rule) - public function addCondition(c:Condition):void { - ruleChains[rcpos][ruleChains[rcpos].length-1].conditions.push(c); - } - - // addStyles <- adds to this.styles public function addStyles(a:Array):void { styles=styles.concat(a); } - + } } diff --git a/net/systemeD/halcyon/styleparser/StyleList.as b/net/systemeD/halcyon/styleparser/StyleList.as index c2fd02bd..97bcd1ad 100644 --- a/net/systemeD/halcyon/styleparser/StyleList.as +++ b/net/systemeD/halcyon/styleparser/StyleList.as @@ -19,8 +19,8 @@ package net.systemeD.halcyon.styleparser { public var shieldStyles:Object={}; public var maxwidth:Number=0; - /** List of sublayers used in this StyleList. */ - public var sublayers:Array=[]; + /** List of subparts used in this StyleList. */ + public var subparts:Array=[]; /** Zoom level this StyleList is valid at. If -1, valid at all styles (i.e. doesn't need to be recomputed on zoom in/out); otherwise, specifies a single zoom level. */ @@ -47,9 +47,9 @@ package net.systemeD.halcyon.styleparser { return NaN; } - /** Record that a sublayer is used in this StyleList. */ - public function addSublayer(s:Number):void { - if (sublayers.indexOf(s)==-1) { sublayers.push(s); } + /** Record that a subpart is used in this StyleList. */ + public function addSubpart(s:String):void { + if (subparts.indexOf(s)==-1) { subparts.push(s); } } /** Summarise StyleList as String - for debugging. */ diff --git a/net/systemeD/potlatch2/BackgroundSelector.mxml b/net/systemeD/potlatch2/BackgroundSelector.mxml index 551e87fc..2c483390 100644 --- a/net/systemeD/potlatch2/BackgroundSelector.mxml +++ b/net/systemeD/potlatch2/BackgroundSelector.mxml @@ -22,6 +22,7 @@ 1) { return new SelectedMultiple(sel); } - else if (sel[0] is Way) { return new SelectedWay(sel[0]); } + var layer:MapPaint=_map.getLayerForEntity(sel[0]); + + if (sel.length>1) { return new SelectedMultiple(sel, layer); } + else if (sel[0] is Way) { return new SelectedWay(sel[0], layer); } else if (sel[0] is Node && Node(sel[0]).hasParentWays) { var way:Way=sel[0].parentWays[0] as Way; return new SelectedWayNode(way, way.indexOfNode(sel[0] as Node)); } else { - return new SelectedPOINode(sel[0] as Node); + return new SelectedPOINode(sel[0] as Node, layer); } } diff --git a/net/systemeD/potlatch2/RelationLoaderPanel.mxml b/net/systemeD/potlatch2/RelationLoaderPanel.mxml index 53c9624e..2a617a14 100644 --- a/net/systemeD/potlatch2/RelationLoaderPanel.mxml +++ b/net/systemeD/potlatch2/RelationLoaderPanel.mxml @@ -21,30 +21,30 @@ import mx.managers.PopUpManager; private var entity:Entity; + private var connection:Connection; private var relid:Number; public function setEntity(e:Entity):void { entity=e; + connection=e.connection; } private function loadRelation():void { relid = Number(requestedID.text); PopUpManager.removePopUp(this); - var conn:Connection = Connection.getConnectionInstance(); if (!relid) return; - if (conn.getRelation(relid)) { + if (connection.getRelation(relid)) { relationLoaded(null); } else { - conn.loadEntityByID("relation",relid); - conn.addEventListener(Connection.LOAD_COMPLETED, relationLoaded); + connection.loadEntityByID("relation",relid); + connection.addEventListener(Connection.LOAD_COMPLETED, relationLoaded); } } private function relationLoaded(event:Event):void { - var conn:Connection = Connection.getConnectionInstance(); - var relation:Relation = conn.getRelation(relid); - conn.removeEventListener(Connection.LOAD_COMPLETED, relationLoaded); + var relation:Relation = connection.getRelation(relid); + connection.removeEventListener(Connection.LOAD_COMPLETED, relationLoaded); if (!relation) return; var undo:CompositeUndoableAction = new CompositeUndoableAction("Add to relation"); diff --git a/net/systemeD/potlatch2/RelationSelectPanel.mxml b/net/systemeD/potlatch2/RelationSelectPanel.mxml index 6cd409ed..f10440d2 100644 --- a/net/systemeD/potlatch2/RelationSelectPanel.mxml +++ b/net/systemeD/potlatch2/RelationSelectPanel.mxml @@ -23,6 +23,7 @@ */ public function init(e:Entity, t:Object):void { entity = e; + conn = e.connection; defaulttags = {}; for (var k:String in t) { //tagmatches[k]=t[k];// deep copy match pattern tags @@ -32,7 +33,6 @@ PopUpManager.centerPopUp(this); var titles:Array = []; - conn = Connection.getConnectionInstance(); relationList = conn.getMatchingRelationIDs(t); if (relationList.length == 0) { warning.text = "No relations available"; diff --git a/net/systemeD/potlatch2/TagGrid.mxml b/net/systemeD/potlatch2/TagGrid.mxml index 399de61d..17b8615f 100644 --- a/net/systemeD/potlatch2/TagGrid.mxml +++ b/net/systemeD/potlatch2/TagGrid.mxml @@ -29,7 +29,7 @@ - + + + + + + + = 0) { // This is slightly hard-coded, but not drastically. The ../s could be changed for string manipulation of the apiBase - var urlBase:String = Connection.getConnectionInstance().apiBase + '../../browse/' + var urlBase:String = connection.apiBase + '../../browse/' navigateToURL(new URLRequest(urlBase+selectedEntity.getType()+'/'+selectedEntity.id), "potlatch_browse"); } } @@ -752,7 +772,7 @@ } private function removeFromRelation(id:Number, index:int=-1):void { - var rel:Relation=Connection.getConnectionInstance().getRelation(id); + var rel:Relation=connection.getRelation(id); if (index>-1) { rel.removeMemberByIndex(index, MainUndoStack.getGlobalStack().addAction); } else if (selectedEntity is EntityCollection) { diff --git a/net/systemeD/potlatch2/Toolbox.mxml b/net/systemeD/potlatch2/Toolbox.mxml index a553ab6a..dcb05c35 100644 --- a/net/systemeD/potlatch2/Toolbox.mxml +++ b/net/systemeD/potlatch2/Toolbox.mxml @@ -223,7 +223,7 @@ public function doDelete():void { var undo:CompositeUndoableAction = new CompositeUndoableAction("Delete objects"); for each (var entity:Entity in controller.state.selection) { - if (entity is Node) { controller.connection.unregisterPOI(Node(entity)); } + if (entity is Node) { entity.connection.unregisterPOI(Node(entity)); } entity.remove(undo.push); } MainUndoStack.getGlobalStack().addAction(undo); diff --git a/net/systemeD/potlatch2/VectorSourceDialog.mxml b/net/systemeD/potlatch2/VectorSourceDialog.mxml index ba041579..e17c8837 100644 --- a/net/systemeD/potlatch2/VectorSourceDialog.mxml +++ b/net/systemeD/potlatch2/VectorSourceDialog.mxml @@ -3,12 +3,12 @@ xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" showCloseButton="true" horizontalAlign="center" title="Load vector file" - width="400" height="350" verticalGap="0"> + width="500" height="350" verticalGap="0"> - + + + + + + + + + @@ -33,7 +45,7 @@ - + @@ -44,20 +56,31 @@ + + + + + + + + + + - - + + =bounds[0] && _map.scale<=bounds[1] && ((_map.edge_l>bounds[3] && _map.edge_lbounds[3] && _map.edge_r2) { _overlay.removeChildAt(2); } - if (!selected.logoData) return; + if (!_selected.logoData) return; var logo:Sprite=new Sprite(); - logo.addChild(new Bitmap(selected.logoData)); - if (selected.logo_url) { logo.buttonMode=true; logo.addEventListener(MouseEvent.CLICK, launchLogoLink, false, 0, true); } + logo.addChild(new Bitmap(_selected.logoData)); + if (_selected.logo_url) { logo.buttonMode=true; logo.addEventListener(MouseEvent.CLICK, launchLogoLink, false, 0, true); } _overlay.addChild(logo); positionLogo(); } private function positionLogo():void { _overlay.getChildAt(2).x=5; - _overlay.getChildAt(2).y=_map.mapheight - 5 - selected.logoHeight - (selected.terms_url ? 10 : 0); + _overlay.getChildAt(2).y=_map.mapheight - 5 - _selected.logoHeight - (_selected.terms_url ? 10 : 0); } private function launchLogoLink(e:Event):void { - if (!selected.logo_url) return; - navigateToURL(new URLRequest(selected.logo_url), '_blank'); + if (!_selected.logo_url) return; + navigateToURL(new URLRequest(_selected.logo_url), '_blank'); } private function setTerms():void { var terms:TextField=TextField(_overlay.getChildAt(1)); - if (!selected.terms_url) { terms.text=''; return; } + if (!_selected.terms_url) { terms.text=''; return; } terms.text="Background terms of use"; positionTerms(); terms.addEventListener(MouseEvent.CLICK, launchTermsLink, false, 0, true); @@ -239,14 +241,14 @@ package net.systemeD.potlatch2.collections { _overlay.getChildAt(1).y=_map.mapheight - 15; } private function launchTermsLink(e:Event):void { - if (!selected.terms_url) return; - navigateToURL(new URLRequest(selected.terms_url), '_blank'); + if (!_selected.terms_url) return; + navigateToURL(new URLRequest(_selected.terms_url), '_blank'); } private function resizeHandler(event:MapEvent):void { - if (selected.logoData) positionLogo(); - if (selected.terms_url) positionTerms(); - if (selected.attribution) positionAttribution(); + if (_selected.logoData) positionLogo(); + if (_selected.terms_url) positionTerms(); + if (_selected.attribution) positionAttribution(); } [Bindable(event="collection_changed")] diff --git a/net/systemeD/potlatch2/collections/Stylesheets.as b/net/systemeD/potlatch2/collections/Stylesheets.as index cc6db63d..1ac3f63d 100644 --- a/net/systemeD/potlatch2/collections/Stylesheets.as +++ b/net/systemeD/potlatch2/collections/Stylesheets.as @@ -14,24 +14,31 @@ package net.systemeD.potlatch2.collections { private static const GLOBAL_INSTANCE:Stylesheets = new Stylesheets(); public static function instance():Stylesheets { return GLOBAL_INSTANCE; } + private static const DEFAULT:String = 'stylesheets/potlatch.css'; + public var collection:Array=[]; - public var selected:Object={}; - private var _map:Map; + private var _selected:String; /* Load catalogue file */ - public function init(map:Map):void { - _map=map; + public function init(request_url:String=null):void { + // First, we set _selected in case it's needed before the stylesheet catalogue loads + _selected=request_url; + _selected=_selected ? _selected : SharedObject.getLocal("user_state").data['stylesheet_url']; + _selected=_selected ? _selected : DEFAULT; + + // Load the stylesheet catalogue var request:DebugURLRequest = new DebugURLRequest("stylesheets.xml"); var loader:URLLoader = new URLLoader(); - loader.addEventListener(Event.COMPLETE, onStylesheetsLoad); + loader.addEventListener(Event.COMPLETE, function(e:Event):void { onStylesheetsLoad(e,request_url); }); loader.load(request.request); } - private function onStylesheetsLoad(event:Event):void { + private function onStylesheetsLoad(event:Event, request_url:String=null):void { var xml:XML = new XML(URLLoader(event.target).data); var saved_url:String = SharedObject.getLocal("user_state").data['stylesheet_url']; var saved_name:String= SharedObject.getLocal("user_state").data['stylesheet_name']; + if (request_url && request_url!=saved_url) { saved_url=request_url; saved_name='Custom'; } var isInMenu:Boolean=false, isSet:Boolean=false; // first, build the menu from the stylesheet list. @@ -64,8 +71,8 @@ package net.systemeD.potlatch2.collections { setStylesheet(s.name, s.url); } else { //hit and hope. FIXME should this be an error state? - collection.push({ name:'Potlatch', url:'stylesheets/potlatch.css'}); - setStylesheet('Potlatch','stylesheets/potlatch.css'); + collection.push({ name:'Potlatch', url:DEFAULT}); + setStylesheet('Potlatch',DEFAULT); } } FunctionKeyManager.instance().registerListener('Map style', @@ -74,12 +81,15 @@ package net.systemeD.potlatch2.collections { } public function setStylesheet(name:String,url:String):void { - _map.setStyle(url); + _selected=url; + dispatchEvent(new CollectionEvent(CollectionEvent.SELECT, url)); var obj:SharedObject = SharedObject.getLocal("user_state"); obj.setProperty("stylesheet_url",url); obj.setProperty("stylesheet_name",name); obj.flush(); } + + public function get selected():String { return _selected; } private function findStylesheetURLWithName(name:String):String { for each (var ss:Object in collection) { diff --git a/net/systemeD/potlatch2/collections/VectorBackgrounds.as b/net/systemeD/potlatch2/collections/VectorBackgrounds.as index 6646e3d8..6381feed 100644 --- a/net/systemeD/potlatch2/collections/VectorBackgrounds.as +++ b/net/systemeD/potlatch2/collections/VectorBackgrounds.as @@ -4,8 +4,9 @@ package net.systemeD.potlatch2.collections { import flash.net.* import flash.system.Security; import net.systemeD.halcyon.Map; + import net.systemeD.halcyon.MapPaint; + import net.systemeD.halcyon.connection.Connection; import net.systemeD.halcyon.DebugURLRequest; - import net.systemeD.halcyon.VectorLayer; import net.systemeD.potlatch2.utils.*; public class VectorBackgrounds extends EventDispatcher { @@ -44,18 +45,25 @@ package net.systemeD.potlatch2.collections { case "KMLImporter": break; case "GPXImporter": - if (set.url) { - if (set.@loaded == "true") { - name ||= 'GPX file'; - var layer:VectorLayer = new VectorLayer(name, _map, 'stylesheets/gpx.css'); - _map.addVectorLayer(layer); - var gpxImporter:GpxImporter = new GpxImporter(layer, layer.paint, [String(set.url)]); - } else { - trace("configured but not loaded isn't supported yet"); - } - } else { - trace("AutoVectorBackground: no url for GPXImporter"); - } + if (set.url) { + if (set.@loaded == "true") { + name ||= 'GPX file'; + var gpx_url:String = String(set.url); + + var connection:Connection = new Connection(name, gpx_url, null, null); + var gpx:GpxImporter=new GpxImporter(connection, _map, [gpx_url], + function(success:Boolean,message:String=null):void { + if (!success) return; + var paint:MapPaint = _map.addLayer(connection, "stylesheets/gpx.css"); + paint.updateEntityUIs(false, false); + dispatchEvent(new Event("layers_changed")); + }, false); + } else { + trace("configured but not loaded isn't supported yet"); + } + } else { + trace("AutoVectorBackground: no url for GPXImporter"); + } break; case "BugLoader": @@ -82,6 +90,18 @@ package net.systemeD.potlatch2.collections { } break; + case "SnapshotLoader": + if (set.url) { + name ||= 'Snapshot Server' + var snapshotLoader:SnapshotLoader = new SnapshotLoader(_map, String(set.url), name); + if (set.@loaded == "true") { + snapshotLoader.load(); + } + } else { + trace("VectorBackground: no url for SnapshotLoader"); + } + break; + default: trace("AutoVectorBackground: unknown loader"); } diff --git a/net/systemeD/potlatch2/controller/ControllerState.as b/net/systemeD/potlatch2/controller/ControllerState.as index 94e60aaa..416216d6 100644 --- a/net/systemeD/potlatch2/controller/ControllerState.as +++ b/net/systemeD/potlatch2/controller/ControllerState.as @@ -7,6 +7,7 @@ package net.systemeD.potlatch2.controller { import net.systemeD.potlatch2.collections.Imagery; import net.systemeD.potlatch2.EditController; import net.systemeD.potlatch2.save.SaveManager; + import net.systemeD.potlatch2.utils.SnapshotConnection; import flash.ui.Keyboard; import mx.controls.Alert; import mx.events.CloseEvent; @@ -19,6 +20,7 @@ package net.systemeD.potlatch2.controller { public class ControllerState { protected var controller:EditController; + public var layer:MapPaint; protected var previousState:ControllerState; protected var _selection:Array=[]; @@ -26,7 +28,8 @@ package net.systemeD.potlatch2.controller { public function ControllerState() {} public function setController(controller:EditController):void { - this.controller = controller; + this.controller=controller; + if (!layer) layer=controller.map.editableLayer; } public function setPreviousState(previousState:ControllerState):void { @@ -48,11 +51,19 @@ package net.systemeD.potlatch2.controller { return this; } + /** Retrieves the map associated with the current EditController */ public function get map():Map { return controller.map; } + /** This is called when the EditController sets this ControllerState as the active state. + * Override this with whatever is needed, such as adding highlights to entities + */ public function enterState():void {} + + /** This is called by the EditController as the current controllerstate is exiting. + * Override this with whatever cleanup is needed, such as removing highlights from entities + */ public function exitState(newState:ControllerState):void {} /** Represent the state in text for debugging. */ @@ -61,11 +72,12 @@ package net.systemeD.potlatch2.controller { } /** Default behaviour for the current state that should be called if state-specific action has been taken care of or ruled out. */ protected function sharedKeyboardEvents(event:KeyboardEvent):ControllerState { + var editableLayer:MapPaint=controller.map.editableLayer; // shorthand for this method switch (event.keyCode) { case 66: setSourceTag(); break; // B - set source tag for current object - case 67: controller.connection.closeChangeset(); break; // C - close changeset - case 68: controller.map.paint.alpha=1.3-controller.map.paint.alpha; return null; // D - dim - case 83: SaveManager.saveChanges(); break; // S - save + case 67: editableLayer.connection.closeChangeset(); break; // C - close changeset + case 68: editableLayer.alpha=1.3-editableLayer.alpha; return null; // D - dim + case 83: SaveManager.saveChanges(editableLayer.connection); break; // S - save case 84: controller.tagViewer.togglePanel(); return null; // T - toggle tags panel case 90: if (!event.shiftKey) { MainUndoStack.getGlobalStack().undo(); return null;}// Z - undo else { MainUndoStack.getGlobalStack().redo(); return null; } // Shift-Z - redo @@ -82,20 +94,41 @@ package net.systemeD.potlatch2.controller { var paint:MapPaint = getMapPaint(DisplayObject(event.target)); var focus:Entity = getTopLevelFocusEntity(entity); + if ( event.type == MouseEvent.MOUSE_UP && focus && map.dragstate!=map.NOT_DRAGGING) { + map.mouseUpHandler(); // in case the end-drag is over an EntityUI + } else if ( event.type == MouseEvent.ROLL_OVER && paint && paint.interactive ) { + paint.setHighlight(focus, { hover: true }); + } else if ( event.type == MouseEvent.MOUSE_OUT && paint && paint.interactive ) { + paint.setHighlight(focus, { hover: false }); + } else if ( event.type == MouseEvent.MOUSE_WHEEL ) { + if (event.delta > 0) { map.zoomIn(); } + else if (event.delta < 0) { map.zoomOut(); } + } + if ( paint && paint.isBackground ) { - if ( event.type == MouseEvent.MOUSE_DOWN && ((event.shiftKey && event.ctrlKey) || event.altKey) ) { + if (event.type == MouseEvent.MOUSE_DOWN && ((event.shiftKey && event.ctrlKey) || event.altKey) ) { // alt-click to pull data out of vector background layer - var newEntity:Entity=paint.findSource().pullThrough(entity,controller.connection); - if (entity is Way) { return new SelectedWay(newEntity as Way); } - else if (entity is Node) { return new SelectedPOINode(newEntity as Node); } - } else if (event.type == MouseEvent.MOUSE_DOWN && entity is Marker) { - return new SelectedMarker(entity as Marker, paint.findSource()); - } else if ( event.type == MouseEvent.MOUSE_UP ) { + var newSelection:Array=[]; + if (selection.indexOf(entity)==-1) { selection=[entity]; } + for each (var entity:Entity in selection) { + paint.setHighlight(entity, { hover:false, selected: false }); + if (entity is Way) paint.setHighlightOnNodes(Way(entity), { selectedway: false }); + newSelection.push(paint.pullThrough(entity,controller.map.editableLayer)); + } + return controller.findStateForSelection(newSelection); + } else if (!paint.interactive) { + return null; + } else if (event.type == MouseEvent.MOUSE_DOWN && paint.interactive) { + if (entity is Way ) { return new SelectedWay(entity as Way, paint); } + else if (entity is Node ) { if (!entity.hasParentWays) return new SelectedPOINode(entity as Node, paint); } + else if (entity is Marker) { return new SelectedMarker(entity as Marker, paint); } + } else if ( event.type == MouseEvent.MOUSE_UP && !event.ctrlKey) { return (this is NoSelection) ? null : new NoSelection(); - } else { return null; } - } - - if ( event.type == MouseEvent.MOUSE_DOWN ) { + } else if ( event.type == MouseEvent.CLICK && focus == null && map.dragstate!=map.DRAGGING && !event.ctrlKey) { + return (this is NoSelection) ? null : new NoSelection(); + } + + } else if ( event.type == MouseEvent.MOUSE_DOWN ) { if ( entity is Node && selectedWay && entity.hasParent(selectedWay) ) { // select node within this way return new DragWayNode(selectedWay, getNodeIndex(selectedWay,entity as Node), event, false); @@ -109,31 +142,12 @@ package net.systemeD.potlatch2.controller { return new DragSelection(selection, event); } else if (entity) { return new DragSelection([entity], event); + } else if (event.ctrlKey && !layer.isBackground) { + return new SelectArea(event.localX,event.localY,selection); } - } else if ( event.type == MouseEvent.CLICK && focus == null && map.dragstate!=map.DRAGGING && this is SelectedMarker) { - // this is identical to the below, but needed for unselecting markers on vector background layers. - // Deselecting a POI or way on the main layer emits both CLICK and MOUSE_UP, but markers only CLICK - // I'll leave it to someone who understands to decide whether they are the same thing and should be - // combined with a (CLICK || MOUSE_UP) - - // "&& this is SelectedMarker" added by Steve Bennett. The CLICK event being processed for SelectedWay state - // causes way to get unselected...so restrict the double processing as much as possible. - + + } else if ( (event.type==MouseEvent.CLICK || event.type==MouseEvent.MOUSE_UP) && focus == null && map.dragstate!=map.DRAGGING && !event.ctrlKey) { return (this is NoSelection) ? null : new NoSelection(); - } else if ( event.type == MouseEvent.MOUSE_UP && focus == null && map.dragstate!=map.DRAGGING) { - return (this is NoSelection) ? null : new NoSelection(); - } else if ( event.type == MouseEvent.MOUSE_UP && focus && map.dragstate!=map.NOT_DRAGGING) { - map.mouseUpHandler(); // in case the end-drag is over an EntityUI - } else if ( event.type == MouseEvent.ROLL_OVER ) { - controller.map.setHighlight(focus, { hover: true }); - } else if ( event.type == MouseEvent.MOUSE_OUT ) { - controller.map.setHighlight(focus, { hover: false }); - } else if ( event.type == MouseEvent.MOUSE_WHEEL ) { - if (event.delta > 0) { - map.zoomIn(); - } else if (event.delta < 0) { - map.zoomOut(); - } } return null; } @@ -204,7 +218,7 @@ package net.systemeD.potlatch2.controller { protected function revertHandler(event:CloseEvent):void { if (event.detail==Alert.CANCEL) return; for each (var item:Entity in _selection) { - controller.connection.loadEntity(item); + item.connection.loadEntity(item); } } diff --git a/net/systemeD/potlatch2/controller/DragSelection.as b/net/systemeD/potlatch2/controller/DragSelection.as index e17299a9..6f583f60 100644 --- a/net/systemeD/potlatch2/controller/DragSelection.as +++ b/net/systemeD/potlatch2/controller/DragSelection.as @@ -95,14 +95,14 @@ package net.systemeD.potlatch2.controller { /** Highlight the dragged selection. */ override public function enterState():void { for each (var entity:Entity in selection) { - controller.map.setHighlight(entity, { selected: true }); + layer.setHighlight(entity, { selected: true }); } } /** Un-highlight the dragged selection. */ override public function exitState(newState:ControllerState):void { for each (var entity:Entity in selection) { - controller.map.setHighlight(entity, { selected: false }); + layer.setHighlight(entity, { selected: false }); } } /** "DragSelection" */ diff --git a/net/systemeD/potlatch2/controller/DragWayNode.as b/net/systemeD/potlatch2/controller/DragWayNode.as index 83421541..b8c7911e 100644 --- a/net/systemeD/potlatch2/controller/DragWayNode.as +++ b/net/systemeD/potlatch2/controller/DragWayNode.as @@ -33,19 +33,13 @@ package net.systemeD.potlatch2.controller { } private function addNode(selectedWay:Way,event:MouseEvent):int { - // find which other ways are under the mouse - var ways:Array=[]; var w:Way; - for each (var wayui:WayUI in controller.map.paint.wayuis) { - w=wayui.hitTest(event.stageX, event.stageY); - if (w && w!=selectedWay) { ways.push(w); } - } - + var ways:Array = layer.findWaysAtPoint(event.stageX, event.stageY, selectedWay); var lat:Number = controller.map.coord2lat(event.localY); var lon:Number = controller.map.coord2lon(event.localX); var undo:CompositeUndoableAction = new CompositeUndoableAction("Insert node"); - var node:Node = controller.connection.createNode({}, lat, lon, undo.push); + var node:Node = selectedWay.connection.createNode({}, lat, lon, undo.push); var index:int = selectedWay.insertNodeAtClosestPosition(node, true, undo.push); - for each (w in ways) { w.insertNodeAtClosestPosition(node, true, undo.push); } + for each (var w:Way in ways) { w.insertNodeAtClosestPosition(node, true, undo.push); } MainUndoStack.getGlobalStack().addAction(undo); return index; } @@ -59,7 +53,7 @@ package net.systemeD.potlatch2.controller { return new SelectedWayNode(parentWay,draggingIndex); } else if (event.shiftKey && !isNew) { // start new way - var way:Way = controller.connection.createWay({}, [entity], + var way:Way = entity.connection.createWay({}, [entity], MainUndoStack.getGlobalStack().addAction); return new DrawWay(way, true, false); } else if (event.shiftKey && isNew) { @@ -115,17 +109,17 @@ package net.systemeD.potlatch2.controller { originalLat = draggingNode.lat; originalLon = draggingNode.lon; - controller.map.setHighlightOnNodes(parentWay, { selectedway: true } ); - controller.map.limitWayDrawing(parentWay, draggingIndex); - controller.map.setHighlight(draggingNode, { selected: true } ); - controller.map.protectWay(parentWay); - controller.map.limitWayDrawing(parentWay, NaN, draggingIndex); + layer.setHighlightOnNodes(parentWay, { selectedway: true } ); + layer.limitWayDrawing(parentWay, draggingIndex); + layer.setHighlight(draggingNode, { selected: true } ); + layer.protectWay(parentWay); + layer.limitWayDrawing(parentWay, NaN, draggingIndex); } override public function exitState(newState:ControllerState):void { - controller.map.unprotectWay(parentWay); - controller.map.limitWayDrawing(parentWay); - controller.map.setHighlightOnNodes(parentWay, { selectedway: false } ); - controller.map.setHighlight(draggingNode, { selected: false } ); + layer.unprotectWay(parentWay); + layer.limitWayDrawing(parentWay); + layer.setHighlightOnNodes(parentWay, { selectedway: false } ); + layer.setHighlight(draggingNode, { selected: false } ); } override public function toString():String { return "DragWayNode"; diff --git a/net/systemeD/potlatch2/controller/DrawQuadrilateral.as b/net/systemeD/potlatch2/controller/DrawQuadrilateral.as index 4fc55890..a7dc4005 100644 --- a/net/systemeD/potlatch2/controller/DrawQuadrilateral.as +++ b/net/systemeD/potlatch2/controller/DrawQuadrilateral.as @@ -54,7 +54,7 @@ package net.systemeD.potlatch2.controller { override public function enterState():void { controller.map.draggable=false; - var conn:Connection=controller.connection; + var conn:Connection=sourceNode.connection; var undo:CompositeUndoableAction = new CompositeUndoableAction("Draw quadrilateral"); var cornerlon:Number =controller.map.coord2lon(controller.map.mouseX); var cornerlat:Number =controller.map.coord2lat(controller.map.mouseY); diff --git a/net/systemeD/potlatch2/controller/DrawWay.as b/net/systemeD/potlatch2/controller/DrawWay.as index 71d98721..f8811f9d 100644 --- a/net/systemeD/potlatch2/controller/DrawWay.as +++ b/net/systemeD/potlatch2/controller/DrawWay.as @@ -31,6 +31,8 @@ package net.systemeD.potlatch2.controller { } override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState { + if (firstSelected.deleted) return new NoSelection(); // in case user has clicked Undo + var mouse:Point; var node:Node; var paint:MapPaint = getMapPaint(DisplayObject(event.target)); @@ -43,8 +45,8 @@ package net.systemeD.potlatch2.controller { controller.map.mouseUpHandler(); // in case you're still in the drag-tolerance zone, and mouse up over something. if ( entity == null || isBackground ) { // didn't hit anything: extend the way by one node. node = createAndAddNode(event, MainUndoStack.getGlobalStack().addAction); - controller.map.setHighlight(node, { selectedway: true }); - controller.map.setPurgable([node], false); + layer.setHighlight(node, { selectedway: true }); + layer.setPurgable([node], false); resetElastic(node); lastClick=node; controller.updateSelectionUIWithoutTagChange(); @@ -56,6 +58,7 @@ package net.systemeD.potlatch2.controller { MainUndoStack.getGlobalStack().undo(); // undo the BeginWayAction that (presumably?) just happened var newPoiAction:CreatePOIAction = new CreatePOIAction( + layer.connection, {}, controller.map.coord2lat(event.localY), controller.map.coord2lon(event.localX)); @@ -75,9 +78,9 @@ package net.systemeD.potlatch2.controller { // hit a node, add it to this way and carry on appendNode(entity as Node, MainUndoStack.getGlobalStack().addAction); if (focus is Way) { - controller.map.setHighlightOnNodes(focus as Way, { hoverway: false }); + layer.setHighlightOnNodes(focus as Way, { hoverway: false }); } - controller.map.setHighlight(entity, { selectedway: true }); + layer.setHighlight(entity, { selectedway: true }); resetElastic(entity as Node); lastClick=entity; if (Way(firstSelected).getNode(0)==Way(firstSelected).getLastNode()) { @@ -91,7 +94,7 @@ package net.systemeD.potlatch2.controller { var lat:Number = controller.map.coord2lat(event.localY); var lon:Number = controller.map.coord2lon(event.localX); var undo:CompositeUndoableAction = new CompositeUndoableAction("Insert node"); - node = controller.connection.createNode({}, lat, lon, undo.push); + node = firstSelected.connection.createNode({}, lat, lon, undo.push); Way(firstSelected).insertNodeAtClosestPosition(node, true, undo.push); appendNode(node,undo.push); MainUndoStack.getGlobalStack().addAction(undo); @@ -101,13 +104,13 @@ package net.systemeD.potlatch2.controller { node = createAndAddNode(event, jnct.push); Way(entity).insertNodeAtClosestPosition(node, true, jnct.push); MainUndoStack.getGlobalStack().addAction(jnct); - controller.map.setHighlight(node, { selectedway: true }); - controller.map.setPurgable([node], false); + layer.setHighlight(node, { selectedway: true }); + layer.setPurgable([node], false); } resetElastic(node); lastClick=node; - controller.map.setHighlightOnNodes(entity as Way, { hoverway: false }); - controller.map.setHighlightOnNodes(firstSelected as Way, { selectedway: true }); + layer.setHighlightOnNodes(entity as Way, { hoverway: false }); + layer.setHighlightOnNodes(firstSelected as Way, { selectedway: true }); } lastClickTime=new Date(); } else if ( event.type == MouseEvent.MOUSE_MOVE && elastic ) { @@ -121,7 +124,7 @@ package net.systemeD.potlatch2.controller { if (focus is Way && focus!=firstSelected) { // floating over another way, highlight its nodes hoverEntity=focus; - controller.map.setHighlightOnNodes(focus as Way, { hoverway: true }); + layer.setHighlightOnNodes(focus as Way, { hoverway: true }); } // set cursor depending on whether we're floating over the start of this way, // another random node, a possible junction... @@ -136,7 +139,7 @@ package net.systemeD.potlatch2.controller { } else if ( event.type == MouseEvent.MOUSE_OUT && !isBackground ) { if (focus is Way && entity!=firstSelected) { hoverEntity=null; - controller.map.setHighlightOnNodes(focus as Way, { hoverway: false }); + layer.setHighlightOnNodes(focus as Way, { hoverway: false }); // ** We could do with an optional way of calling WayUI.redraw to only do the nodes, which would be a // useful optimisation. } @@ -194,7 +197,7 @@ package net.systemeD.potlatch2.controller { protected function stopDrawing():ControllerState { if ( hoverEntity ) { - controller.map.setHighlightOnNodes(hoverEntity as Way, { hoverway: false }); + layer.setHighlightOnNodes(hoverEntity as Way, { hoverway: false }); hoverEntity = null; } @@ -210,7 +213,7 @@ package net.systemeD.potlatch2.controller { var lat:Number = controller.map.coord2lat(event.localY); var lon:Number = controller.map.coord2lon(event.localX); - var node:Node = controller.connection.createNode({}, lat, lon, undo.push); + var node:Node = firstSelected.connection.createNode({}, lat, lon, undo.push); appendNode(node, undo.push); performAction(undo); @@ -243,8 +246,8 @@ package net.systemeD.potlatch2.controller { } // Only actually delete the node if it has no other tags, and is not part of other ways (or part of this way twice) if (node.numParentWays==1 && Way(firstSelected).hasOnceOnly(node) && !node.hasInterestingTags()) { - controller.map.setPurgable([node], true); - controller.connection.unregisterPOI(node); + layer.setPurgable([node], true); + node.connection.unregisterPOI(node); node.remove(undo.push); } @@ -260,7 +263,7 @@ package net.systemeD.potlatch2.controller { performAction(undo); if(!node.isDeleted()) { // i.e. was junction with another way (or is now POI) - controller.map.setHighlight(node, {selectedway: false}); + layer.setHighlight(node, {selectedway: false}); } return state; } @@ -309,7 +312,7 @@ package net.systemeD.potlatch2.controller { appendNode(nextNode as Node, MainUndoStack.getGlobalStack().addAction); resetElastic(nextNode as Node); lastClick=nextNode; - controller.map.setHighlight(nextNode, { selectedway: true }); + layer.setHighlight(nextNode, { selectedway: true }); // recentre the map if the new lat/lon is offscreen controller.map.scrollIfNeeded(nextNode.lat,nextNode.lon); diff --git a/net/systemeD/potlatch2/controller/NoSelection.as b/net/systemeD/potlatch2/controller/NoSelection.as index c65c6f2b..b780bd25 100644 --- a/net/systemeD/potlatch2/controller/NoSelection.as +++ b/net/systemeD/potlatch2/controller/NoSelection.as @@ -25,12 +25,14 @@ package net.systemeD.potlatch2.controller { if (event.type==MouseEvent.MOUSE_UP && (focus==null || (paint && paint.isBackground)) && map.dragstate!=map.DRAGGING) { map.dragstate=map.NOT_DRAGGING; + // ** FIXME: BeginWayAction ought to be a discrete class var undo:CompositeUndoableAction = new BeginWayAction(); - var startNode:Node = controller.connection.createNode( + var conn:Connection = layer.connection; + var startNode:Node = conn.createNode( {}, controller.map.coord2lat(event.localY), controller.map.coord2lon(event.localX), undo.push); - var way:Way = controller.connection.createWay({}, [startNode], undo.push); + var way:Way = conn.createWay({}, [startNode], undo.push); MainUndoStack.getGlobalStack().addAction(undo); return new DrawWay(way, true, false); } diff --git a/net/systemeD/potlatch2/controller/SelectArea.as b/net/systemeD/potlatch2/controller/SelectArea.as new file mode 100644 index 00000000..b00b944b --- /dev/null +++ b/net/systemeD/potlatch2/controller/SelectArea.as @@ -0,0 +1,78 @@ +package net.systemeD.potlatch2.controller { + + import flash.display.*; + import flash.events.*; + import net.systemeD.halcyon.connection.*; + + public class SelectArea extends ControllerState { + + private var startX:Number; + private var startY:Number; + private var endX:Number; + private var endY:Number; + private var box:Shape; + private const TOLERANCE:uint=4; + private var originalSelection:Array; + + public function SelectArea(x:Number,y:Number,sel:Array) { + selection = sel.concat(); + originalSelection = sel.concat(); + startX=endX=x; + startY=endY=y; + } + + override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState { + if (event.type==MouseEvent.MOUSE_MOVE) { + // ** FIXME: weird things happen if you mouse-over the drag-and-drop panel + endX=event.localX; + endY=event.localY; + drawSelectionBox(); + } else if (event.type==MouseEvent.MOUSE_UP) { + // select everything within boundary + var a:Number; + if (startX>endX) { a=startX; startX=endX; endX=a; } + if (startY>endY) { a=startY; startY=endY; endY=a; } + if (endX-startX1) { return this; } return controller.findStateForSelection(selection); + + } else if ( event.type == MouseEvent.MOUSE_UP && selection.indexOf(focus)>-1 ) { + return this; } var cs:ControllerState = sharedMouseEvents(event, entity); return cs ? cs : this; @@ -38,33 +42,27 @@ package net.systemeD.potlatch2.controller { public function mergeWays():ControllerState { var changed:Boolean; var waylist:Array=selectedWays; - var conflictTags:Object={}; + var tagConflict:Boolean=false; + var relationConflict:Boolean=false; var mergers:uint=0; do { // ** FIXME - we should have one CompositeUndoableAction for the whole caboodle, // but that screws up the execution order and can make the merge not work var undo:CompositeUndoableAction = new CompositeUndoableAction("Merge ways"); changed=tryMerge(waylist, undo); - if (changed) - mergers ++; - MainUndoStack.getGlobalStack().addAction(undo); - - if (MergeWaysAction.lastProblemTags) { - for each (var t:String in MergeWaysAction.lastProblemTags) { - conflictTags[t]=t; - } - } - + if (changed) mergers++; + MainUndoStack.getGlobalStack().addAction(undo); + tagConflict ||= MergeWaysAction.lastTagsMerged; + relationConflict||= MergeWaysAction.lastRelationsMerged; + } while (changed==true); if (mergers>0) { - var msg:String = 1 + mergers + " ways merged." - var conflictTags2:Array = new Array(); - // there must be a better way of avoiding duplicates... - for each (var conflict:String in conflictTags) conflictTags2.push(conflict); - if (conflictTags2.length>0) - msg += " *Warning* The following tags conflicted and need attention: " + conflictTags2; - map.connection.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, msg)); + var msg:String = 1 + mergers + " ways merged"; + if (tagConflict && relationConflict) msg+=": check tags and relations"; + else if (tagConflict) msg+=": check conflicting tags"; + else if (relationConflict) msg+=": check relations"; + controller.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, msg)); } return controller.findStateForSelection(waylist); @@ -110,16 +108,16 @@ package net.systemeD.potlatch2.controller { override public function enterState():void { selection=initSelection.concat(); for each (var entity:Entity in selection) { - controller.map.setHighlight(entity, { selected: true, hover: false }); + layer.setHighlight(entity, { selected: true, hover: false }); } controller.updateSelectionUI(); - controller.map.setPurgable(selection,false); + layer.setPurgable(selection,false); } override public function exitState(newState:ControllerState):void { - controller.map.setPurgable(selection,true); + layer.setPurgable(selection,true); for each (var entity:Entity in selection) { - controller.map.setHighlight(entity, { selected: false, hover: false }); + layer.setHighlight(entity, { selected: false, hover: false }); } selection = []; if (!newState.isSelectionState()) { controller.updateSelectionUI(); } diff --git a/net/systemeD/potlatch2/controller/SelectedPOINode.as b/net/systemeD/potlatch2/controller/SelectedPOINode.as index 178f2b00..b3279501 100644 --- a/net/systemeD/potlatch2/controller/SelectedPOINode.as +++ b/net/systemeD/potlatch2/controller/SelectedPOINode.as @@ -1,13 +1,16 @@ package net.systemeD.potlatch2.controller { import flash.events.*; + import flash.display.*; import flash.ui.Keyboard; import net.systemeD.potlatch2.EditController; import net.systemeD.halcyon.connection.*; + import net.systemeD.halcyon.MapPaint; public class SelectedPOINode extends ControllerState { protected var initNode:Node; - public function SelectedPOINode(node:Node) { + public function SelectedPOINode(node:Node, layer:MapPaint=null) { + if (layer) this.layer=layer; initNode = node; } @@ -16,7 +19,7 @@ package net.systemeD.potlatch2.controller { return; clearSelection(this); - controller.map.setHighlight(node, { selected: true }); + layer.setHighlight(node, { selected: true }); selection = [node]; controller.updateSelectionUI(); initNode = node; @@ -24,7 +27,7 @@ package net.systemeD.potlatch2.controller { protected function clearSelection(newState:ControllerState):void { if ( selectCount ) { - controller.map.setHighlight(firstSelected, { selected: false }); + layer.setHighlight(firstSelected, { selected: false }); selection = []; if (!newState.isSelectionState()) { controller.updateSelectionUI(); } } @@ -32,11 +35,14 @@ package net.systemeD.potlatch2.controller { override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState { if (event.type==MouseEvent.MOUSE_MOVE) { return this; } - if (event.type==MouseEvent.MOUSE_DOWN && event.ctrlKey && entity && entity!=firstSelected) { - return new SelectedMultiple([firstSelected,entity]); - } - if (event.type==MouseEvent.MOUSE_DOWN && event.shiftKey && !entity) { + var paint:MapPaint = getMapPaint(DisplayObject(event.target)); + + if (event.type==MouseEvent.MOUSE_DOWN && event.ctrlKey && entity && entity!=firstSelected && paint==layer) { + return new SelectedMultiple([firstSelected,entity],layer); + } else if (event.type==MouseEvent.MOUSE_DOWN && event.shiftKey && !entity && !layer.isBackground) { return new DrawQuadrilateral(firstSelected as Node); + } else if ( event.type == MouseEvent.MOUSE_UP && entity==firstSelected ) { + return this; } var cs:ControllerState = sharedMouseEvents(event, entity); return cs ? cs : this; @@ -53,20 +59,20 @@ package net.systemeD.potlatch2.controller { } public function deletePOI():ControllerState { - controller.connection.unregisterPOI(firstSelected as Node); + firstSelected.connection.unregisterPOI(firstSelected as Node); firstSelected.remove(MainUndoStack.getGlobalStack().addAction); return new NoSelection(); } override public function enterState():void { selectNode(initNode); - controller.map.setPurgable(selection,false); + layer.setPurgable(selection,false); } override public function exitState(newState:ControllerState):void { if(firstSelected.hasTags()) { controller.clipboards['node']=firstSelected.getTagsCopy(); } - controller.map.setPurgable(selection,true); + layer.setPurgable(selection,true); clearSelection(newState); } diff --git a/net/systemeD/potlatch2/controller/SelectedParallelWay.as b/net/systemeD/potlatch2/controller/SelectedParallelWay.as index 94ff8898..dc35494e 100644 --- a/net/systemeD/potlatch2/controller/SelectedParallelWay.as +++ b/net/systemeD/potlatch2/controller/SelectedParallelWay.as @@ -76,7 +76,7 @@ package net.systemeD.potlatch2.controller { /** Creates the WayUI for the parallel way. */ override public function enterState():void { selection=[parallelise.parallelWay]; - controller.map.paint.createWayUI(firstSelected as Way); + layer.createWayUI(firstSelected as Way); startlon =controller.map.coord2lon(controller.map.mouseX); startlatp=controller.map.coord2latp(controller.map.mouseY); } diff --git a/net/systemeD/potlatch2/controller/SelectedWay.as b/net/systemeD/potlatch2/controller/SelectedWay.as index b3aadd88..192e2417 100644 --- a/net/systemeD/potlatch2/controller/SelectedWay.as +++ b/net/systemeD/potlatch2/controller/SelectedWay.as @@ -1,9 +1,10 @@ package net.systemeD.potlatch2.controller { + import flash.display.*; import flash.events.*; import flash.geom.Point; import flash.ui.Keyboard; - import net.systemeD.halcyon.WayUI; + import net.systemeD.halcyon.MapPaint; import net.systemeD.halcyon.connection.*; import net.systemeD.potlatch2.tools.Quadrilateralise; import net.systemeD.potlatch2.tools.Simplify; @@ -20,7 +21,8 @@ package net.systemeD.potlatch2.controller { * @param way The way that is now selected. * @param point The location that was clicked. * @param ways An ordered list of ways sharing a node, to make "way cycling" work. */ - public function SelectedWay(way:Way, point:Point=null, ways:Array=null, index:int=0) { + public function SelectedWay(way:Way, layer:MapPaint=null, point:Point=null, ways:Array=null, index:int=0) { + if (layer) this.layer=layer; initWay = way; clicked = point; wayList = ways; @@ -34,8 +36,8 @@ package net.systemeD.potlatch2.controller { /** Tidy up UI as we transition to a new state without the current selection. */ protected function clearSelection(newState:ControllerState):void { if ( selectCount ) { - controller.map.setHighlight(firstSelected, { selected: false, hover: false }); - controller.map.setHighlightOnNodes(firstSelected as Way, { selectedway: false }); + layer.setHighlight(firstSelected, { selected: false, hover: false }); + layer.setHighlightOnNodes(firstSelected as Way, { selectedway: false }); selection = []; if (!newState.isSelectionState()) { controller.updateSelectionUI(); } } @@ -44,20 +46,23 @@ package net.systemeD.potlatch2.controller { /** Behaviour includes: start drawing a new way, insert a node within this way, select an additional way */ override public function processMouseEvent(event:MouseEvent, entity:Entity):ControllerState { if (event.type==MouseEvent.MOUSE_MOVE || event.type==MouseEvent.ROLL_OVER || event.type==MouseEvent.MOUSE_OUT) { return this; } + var paint:MapPaint = getMapPaint(DisplayObject(event.target)); var focus:Entity = getTopLevelFocusEntity(entity); - if ( event.type == MouseEvent.MOUSE_UP && entity is Node && event.shiftKey ) { + if ( event.type == MouseEvent.MOUSE_UP && entity is Node && event.shiftKey && !layer.isBackground ) { // start new way - var way:Way = controller.connection.createWay({}, [entity], MainUndoStack.getGlobalStack().addAction); + var way:Way = entity.connection.createWay({}, [entity], MainUndoStack.getGlobalStack().addAction); return new DrawWay(way, true, false); - } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Way && focus==firstSelected && event.shiftKey) { + } else if ( event.type == MouseEvent.MOUSE_DOWN && entity is Way && focus==firstSelected && event.shiftKey && !layer.isBackground ) { // insert node within way (shift-click) var d:DragWayNode=new DragWayNode(firstSelected as Way, -1, event, true); d.forceDragStart(); return d; - } else if ( event.type == MouseEvent.MOUSE_DOWN && event.ctrlKey && entity && entity!=firstSelected) { + } else if ( event.type == MouseEvent.MOUSE_DOWN && event.ctrlKey && entity && entity!=firstSelected && paint==layer) { // multiple selection - return new SelectedMultiple([firstSelected,entity]); + return new SelectedMultiple([firstSelected,entity],layer); + } else if ( event.type == MouseEvent.MOUSE_UP && focus==firstSelected ) { + return this; } var cs:ControllerState = sharedMouseEvents(event, entity); return cs ? cs : this; @@ -66,17 +71,20 @@ package net.systemeD.potlatch2.controller { /** Behaviour includes: parallel way, repeat tags, reverse direction, simplify, cycle way selection, delete */ override public function processKeyboardEvent(event:KeyboardEvent):ControllerState { switch (event.keyCode) { - case 80: /* P */ return new SelectedParallelWay(firstSelected as Way); - case 81: /* Q */ Quadrilateralise.quadrilateralise(firstSelected as Way, MainUndoStack.getGlobalStack().addAction); return this; case 82: /* R */ repeatTags(firstSelected); return this; - case 86: /* V */ Way(firstSelected).reverseNodes(MainUndoStack.getGlobalStack().addAction); return this; - case 89: /* Y */ Simplify.simplify(firstSelected as Way, controller.map, true); return this; case 191: /* / */ return cycleWays(); case Keyboard.BACKSPACE: case Keyboard.DELETE: if (event.shiftKey) { return deleteWay(); } break; - case 188: /* , */ return new SelectedWayNode(initWay, initIndex); // allows navigating from one way to another by keyboard - case 190: /* . */ return new SelectedWayNode(initWay, initIndex); // using <, > and / - + } + if (!layer.isBackground) { + switch (event.keyCode) { + case 80: /* P */ return new SelectedParallelWay(firstSelected as Way); + case 81: /* Q */ Quadrilateralise.quadrilateralise(firstSelected as Way, MainUndoStack.getGlobalStack().addAction); return this; + case 86: /* V */ Way(firstSelected).reverseNodes(MainUndoStack.getGlobalStack().addAction); return this; + case 89: /* Y */ Simplify.simplify(firstSelected as Way, controller.map, true); return this; + case 188: /* , */ return new SelectedWayNode(initWay, initIndex); // allows navigating from one way to another by keyboard + case 190: /* . */ return new SelectedWayNode(initWay, initIndex); // using <, > and / + } } var cs:ControllerState = sharedKeyboardEvents(event); return cs ? cs : this; @@ -86,21 +94,17 @@ package net.systemeD.potlatch2.controller { if (!clicked || (wayList && wayList.length<2)) { return this; } if (!wayList) { - wayList=[initWay]; - for each (var wayui:WayUI in controller.map.paint.wayuis) { - var w:Way=wayui.hitTest(clicked.x, clicked.y); - if (w && w!=initWay) { wayList.push(w); } - } + wayList=[initWay].concat(layer.findWaysAtPoint(clicked.x,clicked.y,initWay)); } wayList=wayList.slice(1).concat(wayList[0]); // Find the new way's index of the currently "selected" node, to facilitate keyboard navigation var newindex:int = Way(wayList[0]).indexOfNode(initWay.getNode(initIndex)); - return new SelectedWay(wayList[0], clicked, wayList, newindex); + return new SelectedWay(wayList[0], layer, clicked, wayList, newindex); } /** Perform deletion of currently selected way. */ public function deleteWay():ControllerState { - controller.map.setHighlightOnNodes(firstSelected as Way, {selectedway: false}); + layer.setHighlightOnNodes(firstSelected as Way, {selectedway: false}); selectedWay.remove(MainUndoStack.getGlobalStack().addAction); return new NoSelection(); } @@ -109,13 +113,13 @@ package net.systemeD.potlatch2.controller { override public function enterState():void { if (firstSelected!=initWay) { clearSelection(this); - controller.map.setHighlight(initWay, { selected: true, hover: false }); - controller.map.setHighlightOnNodes(initWay, { selectedway: true }); + layer.setHighlight(initWay, { selected: true, hover: false }); + layer.setHighlightOnNodes(initWay, { selectedway: true }); selection = [initWay]; controller.updateSelectionUI(); initWay.addEventListener(Connection.WAY_REORDERED, updateSelectionUI, false, 0, true); } - controller.map.setPurgable(selection,false); + layer.setPurgable(selection,false); } /** Officially leave the state, remembering the current way's tags for future repeats. */ // TODO: tweak this so that repeat tags aren't remembered if you only select a way in order to branch off it. (a la PL1) @@ -123,7 +127,7 @@ package net.systemeD.potlatch2.controller { if (firstSelected.hasTags()) { controller.clipboards['way']=firstSelected.getTagsCopy(); } - controller.map.setPurgable(selection,true); + layer.setPurgable(selection,true); firstSelected.removeEventListener(Connection.WAY_REORDERED, updateSelectionUI); clearSelection(newState); } diff --git a/net/systemeD/potlatch2/controller/SelectedWayNode.as b/net/systemeD/potlatch2/controller/SelectedWayNode.as index 24e7d22b..8680e1eb 100644 --- a/net/systemeD/potlatch2/controller/SelectedWayNode.as +++ b/net/systemeD/potlatch2/controller/SelectedWayNode.as @@ -25,9 +25,9 @@ package net.systemeD.potlatch2.controller { return; clearSelection(this); - controller.map.setHighlight(way, { hover: false }); - controller.map.setHighlight(node, { selected: true }); - controller.map.setHighlightOnNodes(way, { selectedway: true }); + layer.setHighlight(way, { hover: false }); + layer.setHighlight(node, { selected: true }); + layer.setHighlightOnNodes(way, { selectedway: true }); selection = [node]; parentWay = way; controller.updateSelectionUI(); selectedIndex = index; initIndex = index; @@ -35,9 +35,9 @@ package net.systemeD.potlatch2.controller { protected function clearSelection(newState:ControllerState):void { if ( selectCount ) { - controller.map.setHighlight(parentWay, { selected: false }); - controller.map.setHighlight(firstSelected, { selected: false }); - controller.map.setHighlightOnNodes(parentWay, { selectedway: false }); + layer.setHighlight(parentWay, { selected: false }); + layer.setHighlight(firstSelected, { selected: false }); + layer.setHighlightOnNodes(parentWay, { selectedway: false }); selection = []; if (!newState.isSelectionState()) { controller.updateSelectionUI(); } } @@ -49,7 +49,7 @@ package net.systemeD.potlatch2.controller { if ( event.type == MouseEvent.MOUSE_UP && entity is Node && event.shiftKey ) { // start new way - var way:Way = controller.connection.createWay({}, [entity], + var way:Way = entity.connection.createWay({}, [entity], MainUndoStack.getGlobalStack().addAction); return new DrawWay(way, true, false); } else if ( event.type == MouseEvent.MOUSE_UP && entity is Node && focus == parentWay ) { @@ -97,7 +97,7 @@ package net.systemeD.potlatch2.controller { wayList.splice(wayList.indexOf(parentWay),1); // find index of this node in the newly selected way, to maintain state for keyboard navigation var newindex:int = Way(wayList[0]).indexOfNode(parentWay.getNode(initIndex)); - return new SelectedWay(wayList[0], + return new SelectedWay(wayList[0], layer, new Point(controller.map.lon2coord(Node(firstSelected).lon), controller.map.latp2coord(Node(firstSelected).latp)), wayList.concat(parentWay), @@ -106,13 +106,13 @@ package net.systemeD.potlatch2.controller { override public function enterState():void { selectNode(parentWay,initIndex); - controller.map.setPurgable(selection,false); + layer.setPurgable(selection,false); } override public function exitState(newState:ControllerState):void { if (firstSelected.hasTags()) { controller.clipboards['node']=firstSelected.getTagsCopy(); } - controller.map.setPurgable(selection,true); + layer.setPurgable(selection,true); clearSelection(newState); } @@ -146,8 +146,8 @@ package net.systemeD.potlatch2.controller { if (parentWay.getLastNode() == n) { return this; } } - controller.map.setHighlightOnNodes(parentWay, { selectedway: false } ); - controller.map.setPurgable([parentWay],true); + layer.setHighlightOnNodes(parentWay, { selectedway: false } ); + layer.setPurgable([parentWay],true); MainUndoStack.getGlobalStack().addAction(new SplitWayAction(parentWay, ni)); return new SelectedWay(parentWay); @@ -162,7 +162,7 @@ package net.systemeD.potlatch2.controller { } public function deleteNode():ControllerState { - controller.map.setPurgable(selection,true); + layer.setPurgable(selection,true); firstSelected.remove(MainUndoStack.getGlobalStack().addAction); return new SelectedWay(parentWay); } @@ -174,13 +174,15 @@ package net.systemeD.potlatch2.controller { /** Attempt to either merge the currently selected node with another very nearby node, or failing that, * attach it mid-way along a very nearby way. */ + // FIXME: why are we only merging one node at once? after all, shift-click to insert a node adds into all ways public function join():ControllerState { - var p:Point = new Point(controller.map.lon2coord(Node(firstSelected).lon), - controller.map.latp2coord(Node(firstSelected).latp)); + var p:Point = new Point(controller.map.lon2coord(Node(firstSelected).lon), + controller.map.latp2coord(Node(firstSelected).latp)); var q:Point = map.localToGlobal(p); // First, look for POI nodes in 20x20 pixel box around the current node - var hitnodes:Array = map.connection.getObjectsByBbox( + // FIXME: why aren't we using a hitTest for this? + var hitnodes:Array = layer.connection.getObjectsByBbox( map.coord2lon(p.x-10), map.coord2lon(p.x+10), map.coord2lat(p.y-10), @@ -192,21 +194,17 @@ package net.systemeD.potlatch2.controller { } } - var ways:Array=[]; var w:Way; - for each (var wayui:WayUI in controller.map.paint.wayuis) { - w=wayui.hitTest(q.x, q.y); - if (w && w!=selectedWay) { + var ways:Array=layer.findWaysAtPoint(q.x, q.y, selectedWay); + for each (var w:Way in ways) { // hit a way, now let's see if we hit a specific node - for (var i:uint = 0; i < w.length; i++) { - n = w.getNode(i); - var x:Number = map.lon2coord(n.lon); - var y:Number = map.latp2coord(n.latp); - if (n != selectedNode && Math.abs(x-p.x) + Math.abs(y-p.y) < 10) { - return doMergeNodes(n); - } - } - ways.push(w); - } + for (var i:uint = 0; i < w.length; i++) { + n = w.getNode(i); + var x:Number = map.lon2coord(n.lon); + var y:Number = map.latp2coord(n.latp); + if (n != selectedNode && Math.abs(x-p.x) + Math.abs(y-p.y) < 10) { + return doMergeNodes(n); + } + } } // No nodes hit, so join our node onto any overlapping ways. @@ -217,11 +215,9 @@ package net.systemeD.potlatch2.controller { private function doMergeNodes(n:Node): ControllerState { n.mergeWith(Node(firstSelected), MainUndoStack.getGlobalStack().addAction); // only merge one node at a time - too confusing otherwise? - var msg:String = "Nodes merged." - if (MergeNodesAction.lastProblemTags) { - msg += " *Warning* The following tags conflicted and need attention: " + MergeNodesAction.lastProblemTags; - } - map.connection.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, msg)); + var msg:String = "Nodes merged" + if (MergeNodesAction.lastTagsMerged) msg += ": check conflicting tags"; + controller.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, msg)); return new SelectedWayNode(n.parentWays[0], Way(n.parentWays[0]).indexOfNode(n)); } diff --git a/net/systemeD/potlatch2/mapfeatures/editors/FreeTextEditor.mxml b/net/systemeD/potlatch2/mapfeatures/editors/FreeTextEditor.mxml index 287fe6bb..a7b142ec 100644 --- a/net/systemeD/potlatch2/mapfeatures/editors/FreeTextEditor.mxml +++ b/net/systemeD/potlatch2/mapfeatures/editors/FreeTextEditor.mxml @@ -3,12 +3,13 @@ xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:edit="net.systemeD.potlatch2.mapfeatures.editors.*" xmlns:flexlib="flexlib.controls.*" + xmlns:controls="net.systemeD.controls.*" width="100%" toolTip="{fieldDescription}" direction="{fieldDirection}" styleName="titledEditor"> - diff --git a/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditor.mxml b/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditor.mxml index c5ee5091..69b97026 100644 --- a/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditor.mxml +++ b/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionEditor.mxml @@ -30,8 +30,7 @@ } public function addNewTurnRestriction():void { - var conn:Connection = Connection.getConnectionInstance(); - var relation:Relation = conn.createRelation( + var relation:Relation = _entity.connection.createRelation( { type: 'restriction' }, [ new RelationMember(_entity, 'via') ], MainUndoStack.getGlobalStack().addAction); diff --git a/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionIcon.mxml b/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionIcon.mxml index 40259a26..11465a4a 100644 --- a/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionIcon.mxml +++ b/net/systemeD/potlatch2/mapfeatures/editors/TurnRestrictionIcon.mxml @@ -63,6 +63,7 @@ import net.systemeD.potlatch2.RelationEditorPanel; import net.systemeD.potlatch2.mapfeatures.*; import net.systemeD.halcyon.Map; + import net.systemeD.halcyon.MapPaint; import net.systemeD.halcyon.Globals; import flash.events.*; import mx.collections.ArrayCollection; @@ -161,9 +162,9 @@ } private function setHighlights(bool:Boolean):void { - var map:Map = Globals.vars.root; - var from:Object=findSelected('from'); if (from) { map.setHighlight(from.data, { restrictfrom: bool } ); } - var to:Object =findSelected('to' ); if (to ) { map.setHighlight(to.data , { restrictto: bool } ); } + var paint:MapPaint = Map(Globals.vars.root).editableLayer; // ** FIXME: should really be the mapPaint layer where the objects are located + var from:Object=findSelected('from'); if (from) { paint.setHighlight(from.data, { restrictfrom: bool } ); } + var to:Object =findSelected('to' ); if (to ) { paint.setHighlight(to.data , { restrictto: bool } ); } } ]]> diff --git a/net/systemeD/potlatch2/mygpx/MyGpxDialog.mxml b/net/systemeD/potlatch2/mygpx/MyGpxDialog.mxml index 238815c7..f7120ded 100644 --- a/net/systemeD/potlatch2/mygpx/MyGpxDialog.mxml +++ b/net/systemeD/potlatch2/mygpx/MyGpxDialog.mxml @@ -14,7 +14,6 @@ import net.systemeD.potlatch2.save.*; import net.systemeD.halcyon.Map; import net.systemeD.halcyon.Globals; - import net.systemeD.halcyon.VectorLayer; import net.systemeD.potlatch2.utils.Importer; import net.systemeD.potlatch2.utils.GpxImporter; import mx.controls.Alert; @@ -27,11 +26,12 @@ PopUpManager.centerPopUp(this); this.addEventListener(CloseEvent.CLOSE, myGpxDialog_close); - conn = Connection.getConnectionInstance(); + // >>>> REFACTOR: really horrible way of getting both map and connection map = Globals.vars.root; + conn = map.editableLayer.connection; conn.addEventListener(Connection.TRACES_LOADED, onTracesLoaded); - SaveManager.ensureAccess(fetchList); + SaveManager.ensureAccess(fetchList, conn); } private function myGpxDialog_close(evt:CloseEvent):void { diff --git a/net/systemeD/potlatch2/options/OptionsDialog.mxml b/net/systemeD/potlatch2/options/OptionsDialog.mxml index 8771576c..c98cab8d 100644 --- a/net/systemeD/potlatch2/options/OptionsDialog.mxml +++ b/net/systemeD/potlatch2/options/OptionsDialog.mxml @@ -9,6 +9,9 @@ import mx.events.CloseEvent; import mx.core.Application; import net.systemeD.halcyon.Globals; + import net.systemeD.halcyon.Map; + import net.systemeD.halcyon.connection.Connection; + import net.systemeD.halcyon.connection.StatusFetcher; public function init():void { PopUpManager.addPopUp(this, Application(Application.application), true); @@ -21,6 +24,7 @@ cursorcheck.selected = Application.application.theController.cursorsEnabled; tigercheck.selected = obj.data['tiger_highlighted']; latlongcheck.selected = Application.application.coordsbox.visible; + licencecheck.selected = Map(Globals.vars.root).editableLayer.connection.statusFetcher!=null; } private function optionsDialog_close(evt:CloseEvent):void { @@ -36,6 +40,18 @@ obj.flush(); } + private function licenceToggle():void { + // ** FIXME: this is an inelegant patch for the short-term issue of highlighting licensing status + var conn:Connection=Map(Globals.vars.root).editableLayer.connection; + if (conn.statusFetcher) { + conn.statusFetcher=null; + } else { + conn.statusFetcher=new StatusFetcher("http://wtfe.gryph.de/api/0.6/userlist",conn); + conn.statusFetcher.fetch(conn.getAllLoadedEntities()); + // ** FIXME: needs to also switch map style + } + } + ]]> + + diff --git a/net/systemeD/potlatch2/panels/BackgroundPanel.mxml b/net/systemeD/potlatch2/panels/BackgroundPanel.mxml new file mode 100644 index 00000000..982feb24 --- /dev/null +++ b/net/systemeD/potlatch2/panels/BackgroundPanel.mxml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/net/systemeD/potlatch2/panels/BugPanel.mxml b/net/systemeD/potlatch2/panels/BugPanel.mxml index b5237ce5..0f606d2c 100644 --- a/net/systemeD/potlatch2/panels/BugPanel.mxml +++ b/net/systemeD/potlatch2/panels/BugPanel.mxml @@ -59,13 +59,12 @@ + + + + + @@ -83,12 +88,16 @@ import net.systemeD.halcyon.connection.*; import net.systemeD.halcyon.AttentionEvent; - private var conn:Connection = Connection.getConnectionInstance(); + private var _connection:Connection; private var doSkip:Boolean = false; [Bindable] private var failureText:String = ""; + public function setConnection(connection:Connection):void { + _connection=connection; + } + public function dontPrompt():void { if (processSequence.initialized) { skipInput(); } else { doSkip=true; } } @@ -131,9 +140,9 @@ } // add the listeners - conn.addEventListener(Connection.NEW_CHANGESET, changesetCreated); - conn.addEventListener(Connection.NEW_CHANGESET_ERROR, changesetError); - conn.createChangeset(tags); + _connection.addEventListener(Connection.NEW_CHANGESET, changesetCreated); + _connection.addEventListener(Connection.NEW_CHANGESET_ERROR, changesetError); + _connection.createChangeset(tags); } private function allowForEdit(event:DataGridEvent):void { @@ -159,12 +168,16 @@ } private function changesetCreated(event:EntityEvent=null):void { - var changeset:Changeset = conn.getActiveChangeset(); + var changeset:Changeset = _connection.getActiveChangeset(); addStatus("Saving to changeset "+changeset.id); saveProgress.label = "Uploading changes"; - conn.addEventListener(Connection.SAVE_COMPLETED, saveCompleted); - conn.uploadChanges(); + _connection.addEventListener(Connection.SAVE_COMPLETED, saveCompleted); + var rawData:*=_connection.uploadChanges(); + if (rawData) { + dataText.text=rawData.toString(); + dataButton.visible=true; + } } private function changesetError(event:Event):void { @@ -173,8 +186,8 @@ private function saveCompleted(event:SaveCompleteEvent):void { if ( event.saveOK ) { - conn.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Changes successfully saved")); - close(); + _connection.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Changes successfully saved")); + if (processSequence.selectedChild!=dataTab) close(); } else { fail("Failure when uploading data"); } @@ -193,11 +206,11 @@ processSequence.selectedChild = failureTab; failureText = text; } - + private function close():void { - conn.removeEventListener(Connection.NEW_CHANGESET, changesetCreated); - conn.removeEventListener(Connection.NEW_CHANGESET_ERROR, changesetError); - conn.removeEventListener(Connection.SAVE_COMPLETED, saveCompleted); + _connection.removeEventListener(Connection.NEW_CHANGESET, changesetCreated); + _connection.removeEventListener(Connection.NEW_CHANGESET_ERROR, changesetError); + _connection.removeEventListener(Connection.SAVE_COMPLETED, saveCompleted); PopUpManager.removePopUp(this); } ]]> diff --git a/net/systemeD/potlatch2/save/SaveManager.as b/net/systemeD/potlatch2/save/SaveManager.as index 3e9cdaa8..c2b95685 100644 --- a/net/systemeD/potlatch2/save/SaveManager.as +++ b/net/systemeD/potlatch2/save/SaveManager.as @@ -10,18 +10,19 @@ package net.systemeD.potlatch2.save { public class SaveManager { private static var instance:SaveManager = new SaveManager(); + private var _connection:Connection; - public static function saveChanges():void { - instance.save(instance.saveData); + public static function saveChanges(connection:Connection):void { + instance.save(instance.saveData,connection); } - public static function ensureAccess(callback:Function):void { - instance.save(callback); + public static function ensureAccess(callback:Function, connection:Connection):void { + instance.save(callback,connection); } - private function save(callback:Function):void { - var conn:Connection = Connection.getConnectionInstance(); - if (conn.hasAccessToken()) { + private function save(callback:Function, connection:Connection):void { + _connection=connection; + if (connection.hasAccessToken()) { callback(); } else { getNewToken(callback); @@ -32,6 +33,7 @@ package net.systemeD.potlatch2.save { var oauthPanel:OAuthPanel = OAuthPanel( PopUpManager.createPopUp(Application(Application.application), OAuthPanel, true)); PopUpManager.centerPopUp(oauthPanel); + oauthPanel.setConnection(_connection); var listener:Function = function(event:Event):void { var accessToken:OAuthToken = oauthPanel.accessToken; @@ -49,11 +51,10 @@ package net.systemeD.potlatch2.save { private function saveData():void { var saveDialog:SaveDialog = SaveDialog( PopUpManager.createPopUp(Application(Application.application), SaveDialog, true)); + saveDialog.setConnection(_connection); PopUpManager.centerPopUp(saveDialog); - if (Connection.getConnectionInstance().getActiveChangeset()) { - saveDialog.dontPrompt(); - } + if (_connection.getActiveChangeset()) saveDialog.dontPrompt(); } } diff --git a/net/systemeD/potlatch2/tools/Circularise.as b/net/systemeD/potlatch2/tools/Circularise.as index 6a0cf9f4..88bf1e84 100644 --- a/net/systemeD/potlatch2/tools/Circularise.as +++ b/net/systemeD/potlatch2/tools/Circularise.as @@ -163,7 +163,7 @@ package net.systemeD.potlatch2.tools { var lon:Number = cx+Math.sin(ang*Math.PI/180)*d; lats.splice(index, 0, lat); lons.splice(index, 0, lon); - var newNode:Node = map.connection.createNode({}, map.latp2lat(lat), lon, action.push); + var newNode:Node = way.connection.createNode({}, map.latp2lat(lat), lon, action.push); way.insertNode(index, newNode, action.push); } } diff --git a/net/systemeD/potlatch2/tools/Parallelise.as b/net/systemeD/potlatch2/tools/Parallelise.as index ed49b968..6f231294 100644 --- a/net/systemeD/potlatch2/tools/Parallelise.as +++ b/net/systemeD/potlatch2/tools/Parallelise.as @@ -20,7 +20,7 @@ package net.systemeD.potlatch2.tools { * */ public function Parallelise(way:Way) { var a:Number, b:Number, h:Number, i:uint, j:uint, k:int; - connection = Connection.getConnection(); + connection = way.connection; originalWay = way; parallelWay = connection.createWay({}, [], MainUndoStack.getGlobalStack().addAction); diff --git a/net/systemeD/potlatch2/tools/Quadrilateralise.as b/net/systemeD/potlatch2/tools/Quadrilateralise.as index b050fc47..c8f2f236 100644 --- a/net/systemeD/potlatch2/tools/Quadrilateralise.as +++ b/net/systemeD/potlatch2/tools/Quadrilateralise.as @@ -29,7 +29,6 @@ package net.systemeD.potlatch2.tools { functor.step(); var newScore:Number = functor.goodness; if (newScore > score) { - Connection.getConnection().dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Corners too sharp to straighten")); return false; } score = newScore; diff --git a/net/systemeD/potlatch2/tools/Simplify.as b/net/systemeD/potlatch2/tools/Simplify.as index 3732e254..5e414472 100644 --- a/net/systemeD/potlatch2/tools/Simplify.as +++ b/net/systemeD/potlatch2/tools/Simplify.as @@ -14,7 +14,10 @@ package net.systemeD.potlatch2.tools { * @param way Way to be simplified. * @param map Map it belongs to, for computing offscreen-ness. * @param keepOffscreen If true, don't delete any nodes that are not currently visible. - * */ + * */ + + /* FIXME this should take an action, and push the work onto that. Simplify is called from various places + * so shouldn't be adding to the global undo stack */ public static function simplify(way:Way, map:Map, keepOffscreen:Boolean):void { if (way.length<3) { return; } diff --git a/net/systemeD/potlatch2/utils/BikeShopConnection.as b/net/systemeD/potlatch2/utils/BikeShopConnection.as new file mode 100644 index 00000000..ff262aee --- /dev/null +++ b/net/systemeD/potlatch2/utils/BikeShopConnection.as @@ -0,0 +1,50 @@ +package net.systemeD.potlatch2.utils { + + import net.systemeD.halcyon.connection.Connection; + import net.systemeD.halcyon.connection.Marker; + import com.adobe.serialization.json.JSON; + import flash.system.Security; + import flash.net.*; + import flash.events.*; + + public class BikeShopConnection extends Connection { + + public function BikeShopConnection(cname:String,api:String,policy:String,initparams:Object=null) { + super(cname,api,policy,initparams); + } + + public override function loadBbox(left:Number, right:Number, top:Number, bottom:Number):void { + + // Should be guarded against multiple calls really. + if (policyURL != "") { Security.loadPolicyFile(policyURL); } + + var loader:URLLoader = new URLLoader(); + loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, balls); + loader.addEventListener(Event.COMPLETE, parseKML); + loader.load(new URLRequest(apiBaseURL+"shop/missing.kml?bbox="+left+","+bottom+","+right+","+top)); + } + + public function balls(event:SecurityErrorEvent):void { + } + + private function parseKML(event:Event):void { + //trace(event.target.data); + default xml namespace = new Namespace("http://www.opengis.net/kml/2.2"); + var kml:XML = new XML(event.target.data); + //trace(kml.attributes()); + //var document:XMLList = kml.Document; + for each (var placemark:XML in kml..Placemark) { + trace("name:"+placemark.name); + var coords:Array = placemark..coordinates.split(","); + var lon:Number = coords[0]; + var lat:Number = coords[1]; + //var ele:Number = coords[2]; + var tags:Object = {}; + tags["name"] = String(placemark.name); + tags["description"] = String(placemark.description); + var marker:Marker = createMarker(tags, lat, lon); + } + default xml namespace = new Namespace(""); + } + } +} \ No newline at end of file diff --git a/net/systemeD/potlatch2/utils/BikeShopLoader.as b/net/systemeD/potlatch2/utils/BikeShopLoader.as index 89e4d9f0..87996f3e 100644 --- a/net/systemeD/potlatch2/utils/BikeShopLoader.as +++ b/net/systemeD/potlatch2/utils/BikeShopLoader.as @@ -1,9 +1,9 @@ package net.systemeD.potlatch2.utils { import net.systemeD.halcyon.Map; - import net.systemeD.halcyon.VectorLayer; + import net.systemeD.halcyon.MapPaint; + import net.systemeD.halcyon.connection.Connection; import net.systemeD.halcyon.connection.Marker; - import net.systemeD.potlatch2.BugLayer; import flash.net.*; import flash.events.*; import com.adobe.serialization.json.JSON; @@ -25,56 +25,22 @@ package net.systemeD.potlatch2.utils { private var map:Map; private var bikeShopBaseURL:String; private var name:String; - private var _layer:VectorLayer; + private var _layer:MapPaint; + private var connection:Connection; private static const STYLESHEET:String="stylesheets/bikeshops.css"; public function BikeShopLoader(map:Map, url:String, name:String) { this.map = map; this.bikeShopBaseURL = url; this.name = name; + this.connection = new BikeShopConnection(name,url,bikeShopBaseURL+"crossdomain.xml",null); } public function load():void { - var loader:URLLoader = new URLLoader(); - loader.load(new URLRequest(bikeShopBaseURL+"shop/missing.kml?bbox="+map.edge_l+","+map.edge_b+","+map.edge_r+","+map.edge_t)); - loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, balls); - loader.addEventListener(Event.COMPLETE, parseKML); - } - - public function balls(event:SecurityErrorEvent):void { - trace(event); - } - - private function parseKML(event:Event):void { - //trace(event.target.data); - default xml namespace = new Namespace("http://www.opengis.net/kml/2.2"); - var kml:XML = new XML(event.target.data); - //trace(kml.attributes()); - //var document:XMLList = kml.Document; - for each (var placemark:XML in kml..Placemark) { - trace("name:"+placemark.name); - var coords:Array = placemark..coordinates.split(","); - var lon:Number = coords[0]; - var lat:Number = coords[1]; - //var ele:Number = coords[2]; - var tags:Object = {}; - tags["name"] = String(placemark.name); - tags["description"] = String(placemark.description); - var marker:Marker = layer.createMarker(tags, lat, lon); - } - default xml namespace = new Namespace(""); - layer.paint.updateEntityUIs(layer.getObjectsByBbox(map.edge_l,map.edge_r,map.edge_t,map.edge_b), true, false); - } - - private function get layer():VectorLayer { if (!_layer) { - var policyFile:String = bikeShopBaseURL+"crossdomain.xml"; - Security.loadPolicyFile(policyFile); - - _layer=new VectorLayer(name,map,STYLESHEET); - map.addVectorLayer(_layer); + _layer = map.addLayer(connection, STYLESHEET); } - return _layer; + connection.loadBbox(map.edge_l, map.edge_r, map.edge_t, map.edge_b); } } } \ No newline at end of file diff --git a/net/systemeD/potlatch2/utils/BugLoader.as b/net/systemeD/potlatch2/utils/BugLoader.as index 3ba731fa..4c6f08bb 100644 --- a/net/systemeD/potlatch2/utils/BugLoader.as +++ b/net/systemeD/potlatch2/utils/BugLoader.as @@ -1,9 +1,9 @@ package net.systemeD.potlatch2.utils { import net.systemeD.halcyon.Map; - import net.systemeD.halcyon.VectorLayer; + import net.systemeD.halcyon.MapPaint; import net.systemeD.halcyon.connection.Marker; - import net.systemeD.potlatch2.BugLayer; + import net.systemeD.potlatch2.BugConnection; import flash.net.*; import flash.events.*; import flash.system.Security; @@ -14,9 +14,10 @@ package net.systemeD.potlatch2.utils { private var bugBaseURL:String; private var bugApiKey:String; private var bugDetailsURL:String; - private var _layer:VectorLayer; + private var _layer:MapPaint; private var name:String; private static const STYLESHEET:String="stylesheets/bugs.css"; + private var connection:BugConnection; public function BugLoader(map:Map, url:String, bugApiKey:String, name:String, details:String = ''):void { @@ -25,23 +26,14 @@ package net.systemeD.potlatch2.utils { this.bugApiKey = bugApiKey; this.name = name; this.bugDetailsURL = details; + connection = new BugConnection(name, url, bugApiKey, details); } public function load():void { - layer.loadBbox(map.edge_l, map.edge_r, map.edge_t, map.edge_b); - } - - - private function get layer():VectorLayer { if (!_layer) { - - var policyFile:String = bugBaseURL+"crossdomain.xml"; - Security.loadPolicyFile(policyFile); - - _layer=new BugLayer(name,map,STYLESHEET,bugBaseURL,bugApiKey,bugDetailsURL); - map.addVectorLayer(_layer); + _layer = map.addLayer(connection, STYLESHEET, true, true); } - return _layer; + connection.loadBbox(map.edge_l, map.edge_r, map.edge_t, map.edge_b); } } } \ No newline at end of file diff --git a/net/systemeD/potlatch2/utils/GpxImporter.as b/net/systemeD/potlatch2/utils/GpxImporter.as index 6c6eac4b..c3d64190 100644 --- a/net/systemeD/potlatch2/utils/GpxImporter.as +++ b/net/systemeD/potlatch2/utils/GpxImporter.as @@ -1,6 +1,7 @@ package net.systemeD.potlatch2.utils { - import net.systemeD.halcyon.MapPaint; + import net.systemeD.halcyon.Map; + import net.systemeD.halcyon.connection.Connection; import net.systemeD.halcyon.connection.Node; import net.systemeD.halcyon.connection.Way; import net.systemeD.potlatch2.tools.Simplify; @@ -11,11 +12,11 @@ package net.systemeD.potlatch2.utils { */ public class GpxImporter extends Importer { - public function GpxImporter(container:*, paint:MapPaint, filenames:Array, callback:Function=null, simplify:Boolean=false) { - super(container,paint,filenames,callback,simplify); + public function GpxImporter(connection:Connection, map:Map, filenames:Array, callback:Function=null, simplify:Boolean=false) { + super(connection,map,filenames,callback,simplify); } - override protected function doImport(): void { + override protected function doImport(push:Function): void { var file:XML = new XML(files[0]); for each (var ns:Namespace in file.namespaceDeclarations()) { if (ns.uri.match(/^http:\/\/www\.topografix\.com\/GPX\/1\/[01]$/)) { @@ -27,11 +28,11 @@ package net.systemeD.potlatch2.utils { var way:Way; var nodestring:Array = []; for each (var trkpt:XML in trkseg.trkpt) { - nodestring.push(container.createNode({}, trkpt.@lat, trkpt.@lon)); + nodestring.push(connection.createNode({}, trkpt.@lat, trkpt.@lon, push)); } if (nodestring.length > 0) { - way = container.createWay({}, nodestring); - if (simplify) { Simplify.simplify(way, paint.map, false); } + way = connection.createWay({}, nodestring, push); + if (simplify) { Simplify.simplify(way, map, false); } } } @@ -40,8 +41,8 @@ package net.systemeD.potlatch2.utils { for each (var tag:XML in wpt.children()) { tags[tag.name().localName]=tag.toString(); } - var node:Node = container.createNode(tags, wpt.@lat, wpt.@lon); - container.registerPOI(node); + var node:Node = connection.createNode(tags, wpt.@lat, wpt.@lon, push); + connection.registerPOI(node); } default xml namespace = new Namespace(""); diff --git a/net/systemeD/potlatch2/utils/Importer.as b/net/systemeD/potlatch2/utils/Importer.as index e4b61955..263dacb7 100644 --- a/net/systemeD/potlatch2/utils/Importer.as +++ b/net/systemeD/potlatch2/utils/Importer.as @@ -1,8 +1,7 @@ package net.systemeD.potlatch2.utils { - import net.systemeD.halcyon.MapPaint; + import net.systemeD.halcyon.Map; import net.systemeD.halcyon.ExtendedURLLoader; - import net.systemeD.halcyon.DebugURLRequest; import net.systemeD.halcyon.connection.*; import flash.net.URLLoader; import flash.display.LoaderInfo; @@ -11,8 +10,8 @@ package net.systemeD.potlatch2.utils { public class Importer { - protected var container:Object; // destination object for way/node/relations data - protected var paint:MapPaint; // destination sprite for WayUIs/NodeUIs + protected var connection:Connection; // destination connection for way/node/relations data + protected var map:Map; // map being used - used only in Simplify calls public var files:Array=[]; protected var filenames:Array; @@ -20,42 +19,41 @@ package net.systemeD.potlatch2.utils { protected var callback:Function; protected var simplify:Boolean; - public function Importer(container:*, paint:MapPaint, filenames:Array, callback:Function, simplify:Boolean) { - this.container = container; - this.paint = paint; + public function Importer(connection:Connection, map:Map, filenames:Array, callback:Function, simplify:Boolean) { + this.connection = connection; + this.map = map; this.filenames=filenames; this.callback=callback; this.simplify=simplify; - var sp:uint=0; - for each (var fn:String in filenames) { - var thissp:uint=sp; // scope within this block for the URLLoader 'complete' closure - trace("requesting file "+fn); - var request:DebugURLRequest = new DebugURLRequest(fn); + // Use forEach to avoid closure problem (http://stackoverflow.com/questions/422784/how-to-fix-closure-problem-in-actionscript-3-as3#3971784) + filenames.forEach(function(fn:String, index:int, array:Array):void { + trace("requesting file "+index); + var request:URLRequest = new URLRequest(fn); var loader:URLLoader = new URLLoader(); loader.dataFormat=URLLoaderDataFormat.BINARY; - loader.addEventListener(Event.COMPLETE,function(e:Event):void { fileLoaded(e,thissp); }); + loader.addEventListener(Event.COMPLETE,function(e:Event):void { fileLoaded(e,index); }); if (callback!=null) { loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); } - loader.load(request.request); - sp++; - } + loader.load(request); + }); } protected function fileLoaded(e:Event,filenum:uint):void { trace("loaded file "+filenum); files[filenum]=e.target.data; filesloaded++; - if (filesloaded==filenames.length) { - doImport(); - paint.updateEntityUIs(container.getObjectsByBbox(paint.map.edge_l, paint.map.edge_r, paint.map.edge_t, paint.map.edge_b), false, false); + if (filesloaded==filenames.length) { + var action:CompositeUndoableAction = new CompositeUndoableAction("Import layer "+connection.name); + doImport(action.push); + action.doAction(); // just do it, don't add to undo stack if (callback!=null) { callback(true); } } } - protected function doImport():void { + protected function doImport(push:Function):void { } protected function securityErrorHandler( event:SecurityErrorEvent ):void { callback(false,"You don't have permission to open that file."); } diff --git a/net/systemeD/potlatch2/utils/KmlImporter.as b/net/systemeD/potlatch2/utils/KmlImporter.as index 947240a8..cfed8331 100644 --- a/net/systemeD/potlatch2/utils/KmlImporter.as +++ b/net/systemeD/potlatch2/utils/KmlImporter.as @@ -1,6 +1,7 @@ package net.systemeD.potlatch2.utils { - import net.systemeD.halcyon.MapPaint; + import net.systemeD.halcyon.Map; + import net.systemeD.halcyon.connection.Connection; import net.systemeD.halcyon.connection.Node; import net.systemeD.halcyon.connection.Way; import net.systemeD.halcyon.connection.Relation; @@ -12,11 +13,11 @@ package net.systemeD.potlatch2.utils { */ public class KmlImporter extends Importer { - public function KmlImporter(container:*, paint:MapPaint, filenames:Array, callback:Function=null, simplify:Boolean=false) { - super(container, paint, filenames, callback, simplify); + public function KmlImporter(connection:Connection, map:Map, filenames:Array, callback:Function=null, simplify:Boolean=false) { + super(connection, map, filenames, callback, simplify); } - override protected function doImport(): void { + override protected function doImport(push:Function): void { var kml:XML = new XML(files[0]); for each (var ns:Namespace in kml.namespaceDeclarations()) { @@ -39,15 +40,15 @@ package net.systemeD.potlatch2.utils { } for each (var point:XML in placemark.Point) { - importNode(point.coordinates, tags); + importNode(point.coordinates, tags, push); } for each (var linestring:XML in placemark.LineString) { - importWay(linestring.coordinates, tags, false); + importWay(linestring.coordinates, tags, false, push); } for each (var linearring:XML in placemark.LinearRing) { - importWay(linearring.coordinates, tags, true); + importWay(linearring.coordinates, tags, true, push); } for each (var polygon:XML in placemark.Polygon) { @@ -55,39 +56,39 @@ package net.systemeD.potlatch2.utils { var members:Array = []; var way:Way; - way = importWay(polygon.outerBoundaryIs.LinearRing.coordinates, {}, true); + way = importWay(polygon.outerBoundaryIs.LinearRing.coordinates, {}, true, push); members.push(new RelationMember(way, "outer")); for each (var inner:XML in polygon.innerBoundaryIs) { - way = importWay(inner.LinearRing.coordinates, {}, true); + way = importWay(inner.LinearRing.coordinates, {}, true, push); members.push(new RelationMember(way, "inner")); } tags["type"] = "multipolygon"; - container.createRelation(tags, members); + connection.createRelation(tags, members, push); } else { - importWay(polygon.outerBoundaryIs.LinearRing.coordinates, tags, true); + importWay(polygon.outerBoundaryIs.LinearRing.coordinates, tags, true, push); } } } default xml namespace = new Namespace(""); } - private function importNode(coordinates:String, tags:Object): Node { + private function importNode(coordinates:String, tags:Object, push:Function): Node { var coords:Array = coordinates.split(","); var lon:Number = coords[0]; var lat:Number = coords[1]; //var ele:Number = coords[2]; - var node:Node = container.createNode(tags, lat, lon); + var node:Node = connection.createNode(tags, lat, lon, push); - container.registerPOI(node); + connection.registerPOI(node); return node; } - private function importWay(coordinates:String, tags:Object, polygon:Boolean): Way { + private function importWay(coordinates:String, tags:Object, polygon:Boolean, push:Function): Way { var way:Way; var nodestring:Array = []; @@ -101,7 +102,7 @@ package net.systemeD.potlatch2.utils { var lat:Number = coords[1]; //var ele:Number = coords[2]; - nodestring.push(container.createNode({}, lat, lon)); + nodestring.push(connection.createNode({}, lat, lon, push)); } if (polygon) { @@ -109,8 +110,8 @@ package net.systemeD.potlatch2.utils { } if (nodestring.length > 0) { - way = container.createWay(tags, nodestring); - if (simplify) { Simplify.simplify(way, paint.map, false); } + way = connection.createWay(tags, nodestring, push); + if (simplify) { Simplify.simplify(way, map, false); } } return way; diff --git a/net/systemeD/potlatch2/utils/OsmImporter.as b/net/systemeD/potlatch2/utils/OsmImporter.as index f456899f..c3d84f2f 100644 --- a/net/systemeD/potlatch2/utils/OsmImporter.as +++ b/net/systemeD/potlatch2/utils/OsmImporter.as @@ -1,16 +1,16 @@ package net.systemeD.potlatch2.utils { - import net.systemeD.halcyon.MapPaint; + import net.systemeD.halcyon.Map; import net.systemeD.halcyon.connection.*; import net.systemeD.potlatch2.tools.Simplify; public class OsmImporter extends Importer { - public function OsmImporter(container:*, paint:MapPaint, filenames:Array, callback:Function=null, simplify:Boolean=false) { - super(container,paint,filenames,callback,simplify); + public function OsmImporter(connection:Connection, map:Map, filenames:Array, callback:Function=null, simplify:Boolean=false) { + super(connection,map,filenames,callback,simplify); } - override protected function doImport():void { + override protected function doImport(push:Function):void { var map:XML = new XML(files[0]); var data:XML; @@ -23,14 +23,14 @@ package net.systemeD.potlatch2.utils { for each(data in map.node) { oldid = Number(data.@id); - nodemap[oldid] = container.createNode(parseTags(data.tag), Number(data.@lat), Number(data.@lon)); + nodemap[oldid] = connection.createNode(parseTags(data.tag), Number(data.@lat), Number(data.@lon), push); } for each(data in map.way) { oldid = Number(data.@id); var nodes:Array = []; for each(var nd:XML in data.nd) { nodes.push(nodemap[Number(nd.@ref)]); } - waymap[oldid] = container.createWay(parseTags(data.tag), nodes); + waymap[oldid] = connection.createWay(parseTags(data.tag), nodes, push); } for each(data in map.relation) { @@ -48,7 +48,7 @@ package net.systemeD.potlatch2.utils { } if (member!=null) { members.push(new RelationMember(member,role)); } } - relationmap[oldid] = container.createRelation(parseTags(data.tag), members); + relationmap[oldid] = connection.createRelation(parseTags(data.tag), members, push); } } diff --git a/net/systemeD/potlatch2/utils/ShpImporter.as b/net/systemeD/potlatch2/utils/ShpImporter.as index 6c46fab6..dd0cf6ce 100644 --- a/net/systemeD/potlatch2/utils/ShpImporter.as +++ b/net/systemeD/potlatch2/utils/ShpImporter.as @@ -2,46 +2,81 @@ package net.systemeD.potlatch2.utils { import org.vanrijkom.shp.*; import org.vanrijkom.dbf.*; - import net.systemeD.halcyon.MapPaint; + import com.gradoservice.proj4as.*; + import net.systemeD.halcyon.Map; + import net.systemeD.halcyon.connection.Connection; import net.systemeD.halcyon.connection.Node; import net.systemeD.halcyon.connection.Way; import net.systemeD.potlatch2.tools.Simplify; public class ShpImporter extends Importer { - public function ShpImporter(container:*, paint:MapPaint, filenames:Array, callback:Function=null, simplify:Boolean=false) { - super(container,paint,filenames,callback,simplify); + private var projection:String; + + public function ShpImporter(connection:Connection, map:Map, filenames:Array, callback:Function=null, simplify:Boolean=false, projection:String="") { + if (projection!='') this.projection=projection; + super(connection,map,filenames,callback,simplify); } - override protected function doImport(): void { + override protected function doImport(push:Function): void { // we load .shp as files[0], .shx as files[1], .dbf as files[2] var shp:ShpHeader=new ShpHeader(files[0]); var dbf:DbfHeader=new DbfHeader(files[2]); + if (projection) { + var proj:Proj4as=new Proj4as(); + var toProj:ProjProjection=new ProjProjection('EPSG:4326'); + var fromProj:ProjProjection=new ProjProjection('EPSG:27700'); + } + + var nodemap:Object={}; + var key:String, v:String; + if (shp.shapeType==ShpType.SHAPE_POLYGON || shp.shapeType==ShpType.SHAPE_POLYLINE) { // Loop through all polylines in the shape var polyArray:Array = ShpTools.readRecords(files[0]); for (var i:uint=0; i0) { - way=container.createWay({}, nodestring); - if (simplify) { Simplify.simplify(way, paint.map, false); } + way=connection.createWay(tags, nodestring, push); + if (simplify) { Simplify.simplify(way, map, false); } } } } diff --git a/net/systemeD/potlatch2/utils/SnapshotConnection.as b/net/systemeD/potlatch2/utils/SnapshotConnection.as new file mode 100644 index 00000000..4910cf5f --- /dev/null +++ b/net/systemeD/potlatch2/utils/SnapshotConnection.as @@ -0,0 +1,36 @@ +package net.systemeD.potlatch2.utils { + + import net.systemeD.halcyon.connection.*; + import flash.events.Event; + import flash.net.*; + + public class SnapshotConnection extends XMLConnection { + + public function SnapshotConnection(cname:String,api:String,policy:String,initparams:Object=null) { + super(cname,api,policy,initparams); + } + + /** Send a "complete" call to the server, and remove it from the current layer */ + public function markComplete(entity:Entity):void { + if (entity is Node) { + var node:Node = Node(entity); + if (node == getNode(node.id)) { // confirm it's from this connection + var urlReq:URLRequest = new URLRequest(apiBaseURL+"node/"+node.id+"/complete"); + urlReq.method = "POST"; + urlReq.data = ' '; + urlReq.contentType = "application/xml"; + urlReq.requestHeaders = [ new URLRequestHeader("X_HTTP_METHOD_OVERRIDE", "PUT"), + new URLRequestHeader("X-Error-Format", "XML") ]; + var loader:URLLoader = new URLLoader(); + loader.addEventListener(Event.COMPLETE, function(e:Event):void { killNode(node.id) }); + loader.load(urlReq); + } + + } else if (entity is Way) { + var way:Way = Way(entity); + trace("not implemented"); + } + } + + } +} \ No newline at end of file diff --git a/net/systemeD/potlatch2/utils/SnapshotLoader.as b/net/systemeD/potlatch2/utils/SnapshotLoader.as new file mode 100644 index 00000000..b0feaed1 --- /dev/null +++ b/net/systemeD/potlatch2/utils/SnapshotLoader.as @@ -0,0 +1,27 @@ +package net.systemeD.potlatch2.utils { + + import net.systemeD.halcyon.Map; + import net.systemeD.halcyon.MapPaint; + import net.systemeD.potlatch2.utils.SnapshotConnection; + + public class SnapshotLoader { + + private var map:Map; + private var _layer:MapPaint; + private static const STYLESHEET:String="stylesheets/wireframe.css"; //TODO take from xml + private var connection:SnapshotConnection; + + + public function SnapshotLoader(map:Map, url:String, name:String):void { + this.map = map; + connection = new SnapshotConnection(name, url, ''); + } + + public function load():void { + if (!_layer) { + _layer = map.addLayer(connection, STYLESHEET); + } + connection.loadBbox(map.edge_l, map.edge_r, map.edge_t, map.edge_b); + } + } +} \ No newline at end of file diff --git a/net/systemeD/potlatch2/utils/TrackLoader.as b/net/systemeD/potlatch2/utils/TrackLoader.as index d4bc6ece..a476d757 100644 --- a/net/systemeD/potlatch2/utils/TrackLoader.as +++ b/net/systemeD/potlatch2/utils/TrackLoader.as @@ -2,7 +2,7 @@ package net.systemeD.potlatch2.utils { import net.systemeD.halcyon.connection.*; import net.systemeD.halcyon.Map; - import net.systemeD.halcyon.VectorLayer; + import net.systemeD.halcyon.MapPaint; import flash.net.*; import flash.events.*; @@ -10,6 +10,7 @@ package net.systemeD.potlatch2.utils { - empty layer on reload - cope with tracks with timestamps */ + /** A TrackLoader will load the public GPX traces for the current map bounding box into a separate layer */ public class TrackLoader { private var left:Number=0; @@ -20,14 +21,24 @@ package net.systemeD.potlatch2.utils { private var map:Map; private var apiBaseURL:String; + private var connection:Connection; /* to store the nodes/ways that are faked up for GPX tracks */ private static const STYLESHEET:String="stylesheets/gpx.css"; - + private static const LAYER:String="GPS tracks"; + + /** Create a new TrackLoader + * @param map The map object you want to the GPS tracks layer to be added to + * @param url The url of the server api base + */ public function TrackLoader(map:Map, url:String) { this.map=map; apiBaseURL=url; + connection = new Connection(LAYER,apiBaseURL,null, null); } - + + /** Load the public traces for the current map extent + * @param keep not implemented + */ public function load(keep:Boolean=false):void { if (map.edge_l==left && map.edge_r==right && map.edge_t==top && map.edge_b==bottom) { page++; @@ -37,16 +48,18 @@ package net.systemeD.potlatch2.utils { top =map.edge_t; bottom=map.edge_b; page=0; - if (!keep) { } // ** TODO: blank the vector layer + if (!keep) { } // ** TODO: blank the connection objects } + /* This isn't great - conceptially it would be nicer for the connection to do the request */ var loader:URLLoader = new URLLoader(); loader.load(new URLRequest(apiBaseURL+"trackpoints?bbox="+left+","+bottom+","+right+","+top+"&page="+page)); loader.addEventListener(Event.COMPLETE, parseGPX); } - public function parseGPX(event:Event):void { + private function parseGPX(event:Event):void { var file:XML = new XML(event.target.data); + var action:CompositeUndoableAction = new CompositeUndoableAction("add track objects"); for each (var ns:Namespace in file.namespaceDeclarations()) { if (ns.uri.match(/^http:\/\/www\.topografix\.com\/GPX\/1\/[01]$/)) { default xml namespace = ns; @@ -61,26 +74,26 @@ package net.systemeD.potlatch2.utils { lat = trkpt.@lat; lon = trkpt.@lon; if (lastlat && nodestring.length > 0 && greatCircle(lat, lon, lastlat, lastlon) > 30) { - layer.createWay({}, nodestring); + connection.createWay({}, nodestring, action.push); nodestring = []; } - nodestring.push(layer.createNode({}, lat, lon)); + nodestring.push(connection.createNode({}, lat, lon, action.push)); lastlat = lat; lastlon = lon; } - if (nodestring.length > 0) { layer.createWay({}, nodestring); } + if (nodestring.length > 0) { connection.createWay({}, nodestring, action.push); trace("create way");} } - + + action.doAction(); /* just do it, don't add to undo stack */ default xml namespace = new Namespace(""); - layer.paint.updateEntityUIs(layer.getObjectsByBbox(left,right,top,bottom), false, false); + layer.updateEntityUIs(false, false); } - private function get layer():VectorLayer { - var vl:VectorLayer=map.findVectorLayer('GPS tracks'); - if (!vl) { - vl=new VectorLayer('GPS tracks',map,STYLESHEET); - map.addVectorLayer(vl); - } - return vl; + private function get layer():MapPaint { + var mp:MapPaint = map.findLayer(LAYER); + if (!mp) { + mp = map.addLayer(connection, STYLESHEET); + } + return mp; } private function greatCircle(lat1:Number,lon1:Number,lat2:Number,lon2:Number):Number { diff --git a/potlatch2.mxml b/potlatch2.mxml index 368b7d38..4754b11b 100644 --- a/potlatch2.mxml +++ b/potlatch2.mxml @@ -7,7 +7,7 @@ horizontalScrollPolicy="off" verticalScrollPolicy="off" horizontalAlign="center" - addedToStage="initApp()" + addedToStage="startInit()" preloader="net.systemeD.potlatch2.Preloader"> @@ -37,7 +37,7 @@ else if (event.index==1) { new MyGpxDialog().init(); } else { mx.controls.Menu(gpsButton.popUp).selectedIndex=0; mx.controls.Menu(gpsButton.popUp).selectedIndex=0; /* Yes, we do need to call it twice */ - theMap.removeVectorLayer(theMap.findVectorLayer('GPS tracks')); }"> + theMap.removeLayerByName('GPS tracks'); }"> @@ -51,12 +51,13 @@ enabled="{MainUndoStack.getGlobalStack().canUndo()}" toolTip="{MainUndoStack.getGlobalStack().getUndoDescription() ? 'Undo '+MainUndoStack.getGlobalStack().getUndoDescription() : 'Undo last action'}" /> + enabled="{MainUndoStack.getGlobalStack().canRedo()}" + toolTip="{MainUndoStack.getGlobalStack().getRedoDescription() ? 'Redo '+MainUndoStack.getGlobalStack().getRedoDescription() : 'Redo last action'}" /> + click="SaveManager.saveChanges(theMap.editableLayer.connection);" id="saveButton" enabled="false"/> @@ -125,18 +126,29 @@ include "version.as"; - private function initApp():void { - + private function startInit():void { /* For reasons that I don't yet understand, the en_US locale is the default and doesn't work at all. */ /* Change this to another locale (e.g. fr_FR) to see the differences. */ if (loaderInfo.parameters['locale']) { dispatchEvent(new LocaleEvent(LocaleEvent.LOAD_LOCALE,loaderInfo.parameters['locale'])); } + // Load font and don't do anything until that's done + var loader:Loader = new Loader(); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, continueInit); + loader.load(new URLRequest("FontLibrary.swf")); + } + + private function continueInit(r:Event):void { + // Set font + var FontLibrary:Class = r.target.applicationDomain.getDefinition("FontLibrary") as Class; + Font.registerFont(FontLibrary.DejaVu); + Globals.vars.map_area = map_area; Globals.vars.root = map_area.rawChildren; // set up global reference to root level var _root:IChildList = map_area.rawChildren; // convenient local shorthand Globals.vars.nocache = loaderInfo.parameters['nocache'] == 'true'; + Globals.vars.flashvars = loaderInfo.parameters; // populate sharedObject with loaderInfo parameters if supplied var obj:SharedObject = SharedObject.getLocal("user_state"); @@ -165,28 +177,46 @@ // pass additional parameters to map var params:Object={}; var k:String; - for (k in this.loaderInfo.parameters) { - params[k]=this.loaderInfo.parameters[k]; - } - for (k in SharedObject.getLocal('user_state').data) { - params[k]=SharedObject.getLocal('user_state').data[k]; - } - params['tileblocks']=[new RegExp("google","i")]; // hard-coded block on Google tiles - - // create map and Yahoo - theMap=new Map(params); - - // Auto-load vector backgrounds from config - // This could go spectacularly wrong if map.init completes before the event listener below is added - theMap.addEventListener(MapEvent.INITIALISED, function(e:Event):void { VectorBackgrounds.instance().init(theMap); }); + for (k in this.loaderInfo.parameters) params[k]=this.loaderInfo.parameters[k]; + for (k in SharedObject.getLocal('user_state').data) params[k]=SharedObject.getLocal('user_state').data[k]; + // Create map + theMap=new Map(); theMap.backdrop=b; + theMap.updateSize(w,h); + theMap.addEventListener(MapEvent.SCALE, scaleHandler); + yahoo=new Yahoo(theMap); yahoo.hide(); _root.addChild(yahoo); _root.addChild(theMap); - theMap.updateSize(w,h); - theMap.addEventListener(MapEvent.SCALE, scaleHandler); + + // Initialise stylesheets + Stylesheets.instance().init(); + Stylesheets.instance().addEventListener(CollectionEvent.SELECT, + function(e:CollectionEvent):void { theMap.editableLayer.setStyle(String(e.data)); } + ); + + // Add core data layer + var conn:Connection = new XMLConnection("Main", params['api'], params['policy'], params); + conn.addEventListener(Connection.LOAD_STARTED, onDataStart); + conn.addEventListener(Connection.LOAD_COMPLETED, onDataComplete); + conn.addEventListener(Connection.SAVE_STARTED, onDataStart); + conn.addEventListener(Connection.SAVE_COMPLETED, onDataComplete); + conn.addEventListener(Connection.DATA_DIRTY, onDataDirty); + conn.addEventListener(Connection.DATA_CLEAN, onDataClean); + conn.addEventListener(MapEvent.ERROR, onMapError); + conn.addEventListener(AttentionEvent.ATTENTION, onAttention); + conn.addEventListener(AttentionEvent.ALERT, onAlert); + theMap.addLayer(conn, Stylesheets.instance().selected, false, true); + + // Auto-load vector backgrounds from config + theMap.addEventListener(MapEvent.INITIALISED, function(e:Event):void { VectorBackgrounds.instance().init(theMap); }); + + // Set start position of map + // ** FIXME: if lat/lon/zoom aren't supplied, we need to keep the map in a non-loading state + // until the user has decided where to start editing (e.g. when the first GPX loads) + theMap.init(params['lat'], params['lon'], params['zoom']); // add attribution/logo sprite var overlay:Sprite=new Sprite(); @@ -209,10 +239,15 @@ Globals.vars.map_area.addEventListener(MouseEvent.MOUSE_MOVE, theMap.mouseMoveHandler); Globals.vars.map_area.addEventListener(MouseEvent.MOUSE_DOWN, theMap.mouseDownHandler); - // initialise imagery and stylesheets + // initialise imagery + theMap.tileset.blocks=[new RegExp("google","i")]; // hard-coded block on Google tiles + theMap.tileset.setDimming(params['background_dim'] ==null ? true : params['background_dim']); + theMap.tileset.setSharpen(params['background_sharpen']==null ? false : params['background_sharpen']); Imagery.instance().init(theMap, overlay, yahoo); - Stylesheets.instance().init(theMap); Imagery.instance().addEventListener(MapEvent.BUMP, bumpHandler); + Imagery.instance().addEventListener(CollectionEvent.SELECT, + function(e:CollectionEvent):void { theMap.tileset.init(e.data, e.data!=''); } + ); // keyboard event attached to stage stage.addEventListener(KeyboardEvent.KEY_UP, theMap.keyUpHandler); @@ -235,20 +270,12 @@ t.visible = loaderInfo.parameters["show_debug"] == 'true'; Globals.vars.root=theMap; // just for the addDebug function + // create controller theController = new EditController(theMap, tagViewer, toolbox); theController.setActive(); + theController.addEventListener(AttentionEvent.ATTENTION, onAttention); + theController.addEventListener(AttentionEvent.ALERT, onAlert); - var conn:Connection = Connection.getConnectionInstance(); - conn.addEventListener(Connection.LOAD_STARTED, onDataStart); - conn.addEventListener(Connection.LOAD_COMPLETED, onDataComplete); - conn.addEventListener(Connection.SAVE_STARTED, onDataStart); - conn.addEventListener(Connection.SAVE_COMPLETED, onDataComplete); - conn.addEventListener(Connection.DATA_DIRTY, onDataDirty); - conn.addEventListener(Connection.DATA_CLEAN, onDataClean); - conn.addEventListener(MapEvent.ERROR, onMapError); - conn.addEventListener(AttentionEvent.ATTENTION, onAttention); - conn.addEventListener(AttentionEvent.ALERT, onAlert); - // set the access token from saved cookie var tokenObject:SharedObject = SharedObject.getLocal("access_token"); conn.setAccessToken(tokenObject.data["oauth_token"], tokenObject.data["oauth_token_secret"]); @@ -259,20 +286,23 @@ if (conn.hasAccessToken()) { loadTrace(id); } else { - SaveManager.ensureAccess(function ():void { loadTrace(id); }); + SaveManager.ensureAccess(function ():void { loadTrace(id); }, conn); } } // Load arbitrary GPX from provided URL if (loaderInfo.parameters['gpx_url']) { - var vectorlayer:VectorLayer=new VectorLayer(name,theMap,"stylesheets/gpx.css"); - vectorlayer.url=loaderInfo.parameters['gpx_url']; - var gpx:GpxImporter=new GpxImporter(vectorlayer, vectorlayer.paint, [vectorlayer.url], - function(success:Boolean,message:String=null):void { - if (!success) return; - theMap.addVectorLayer(vectorlayer); - dispatchEvent(new Event("layers_changed")); - }, false); + + var gpx_url:String = loaderInfo.parameters['gpx_url']; + + var connection:Connection = new Connection(name, gpx_url, null, null); + var gpx:GpxImporter=new GpxImporter(connection, theMap, [gpx_url], + function(success:Boolean,message:String=null):void { + if (!success) return; + var paint:MapPaint = theMap.addLayer(connection, "stylesheets/gpx.css"); + paint.updateEntityUIs(false, false); + dispatchEvent(new Event("layers_changed")); + }, false); } // create GPS trackloader @@ -282,7 +312,7 @@ // force_auth == force => checks for access token, and displays OAuth panel if needed var force_auth:String = loaderInfo.parameters["force_auth"]; if (!conn.hasAccessToken() && force_auth == 'force') { - SaveManager.ensureAccess(onAccessChecked); + SaveManager.ensureAccess(onAccessChecked, conn); } // show help dialog on startup, if required @@ -448,11 +478,12 @@ var createAction:CompositeUndoableAction = new CompositeUndoableAction("Create POI"); - var node:Node = Connection.getConnectionInstance().createNode({}, lat, lon, createAction.push); + var conn:Connection = theMap.editableLayer.connection; + var node:Node = conn.createNode({}, lat, lon, createAction.push); for each ( var tag:Object in tags ) { node.setTag(tag.k, tag.v, createAction.push); } - Connection.getConnectionInstance().registerPOI(node); + conn.registerPOI(node); MainUndoStack.getGlobalStack().addAction(createAction); theController.setState(new SelectedPOINode(node)); } @@ -463,7 +494,7 @@ } private function loadTrace(id:Number):void { - var conn:Connection = Connection.getConnectionInstance(); + var conn:Connection = theMap.editableLayer.connection; conn.addEventListener(Connection.TRACES_LOADED, function (e:Event):void { for each (var trace:Trace in conn.getTraces()) { if (trace.id == id) { trace.addToMap(); } diff --git a/resources/stylesheets/core_interactive.css b/resources/stylesheets/core_interactive.css index 0e4ae871..0c02f611 100644 --- a/resources/stylesheets/core_interactive.css +++ b/resources/stylesheets/core_interactive.css @@ -4,10 +4,12 @@ relation[type=restriction] node { z-index:11; icon-image: icons/restriction.png; /* Interactive way behaviour */ -way :hover { z-index: 2; width: eval('_width+10'); color: #ffff99; } -way :selected { z-index: 2; width: eval('_width+10'); color: yellow; opacity: 0.7;} -way :restrictfrom { z-index: -1; width: eval('_width+10'); color: red; opacity: 0.7;} -way :restrictto { z-index: -1; width: eval('_width+10'); color: blue; opacity: 0.7;} +way::highlight :hover { z-index: 2; width: eval('_width+10'); color: #ffff99; } +way::highlight :hover :background { color: lightcyan; } +way::highlight :selected { z-index: 2; width: eval('_width+10'); color: yellow; opacity: 0.7;} +way::highlight :selected :background { color: cyan; } +way::highlight :restrictfrom { z-index: -1; width: eval('_width+10'); color: red; opacity: 0.7;} +way::highlight :restrictto { z-index: -1; width: eval('_width+10'); color: blue; opacity: 0.7;} /*way !:drawn !:hasTags{ z-index:10; width: 0.5; color: red; }*/ way !:drawn { z-index:10; width: 1; color: #333333; } way :tiger { casing-color: #ff00ff;} @@ -20,5 +22,6 @@ node !:drawn :poi { z-index: 2; icon-image: circle; icon-width: 4; color: green; node !:drawn :hasTags { z-index: 9; icon-image: circle; icon-width: 4; color: black; } node :hasTags :selectedway { z-index: 9; icon-image: square; icon-width: 8; color: black; layer: 5; } node !:drawn :selectedway { z-index: 9; icon-image: square; icon-width: 8; color: red; casing-color: #cc0000; casing-width: 1; layer: 5; } -node :selected { z-index: 1; icon-image: square; icon-width: eval('_width+10'); color: yellow; interactive: no; layer: 5; } -node :junction :selectedway { z-index: 8; icon-image: square; icon-width: 12; casing-color: black; casing-width: 1; layer: 5; } +node::selectedNode :selected { z-index: 1; icon-image: square; icon-width: eval('_width+10'); color: yellow; interactive: no; layer: 5; } +node::selectedNode :selected :background { color: cyan; } +node::junctionNode :junction :selectedway { z-index: 8; icon-image: square; icon-width: 12; casing-color: black; casing-width: 1; layer: 5; } diff --git a/resources/stylesheets/core_landuse.css b/resources/stylesheets/core_landuse.css index 7eeb1173..a113cc7e 100644 --- a/resources/stylesheets/core_landuse.css +++ b/resources/stylesheets/core_landuse.css @@ -28,10 +28,10 @@ way[leisure=pitch] :area { z-index: 6; color: #88bb44; width: 2; fill-color: way[landuse=recreation_ground] :area { color: green; fill-color: green; set .area_small_name;} way[amenity=parking] :area { color: #bbaa66; width: 1; fill-color: #bbaa66; fill-opacity: 0.2; } way[public_transport=pay_scale_area] :area { color: gray; width: 1; fill-color: gray; fill-opacity: 0.1; } -way[man_made=pier] { z-index: 4; color: #777; width: 3; casing-color: black; casing-width: 5;} +way[man_made=pier] { z-index: 4; color: #777; width: 3; casing-color: black; casing-width: 1;} way[man_made=pier][floating=yes] { dashes: 4,2; casing-color: #444;} way[leisure=marina] :area { color: pink; fill-color: pink; fill-opacity: 0.4; set .area_small_name;} -way[leisure=slipway] { color: grey; width: 3; casing-color: blue; casing-width: 7; } +way[leisure=slipway] { color: grey; width: 3; casing-color: blue; casing-width: 2; } way[leisure=golf_course] :area { color: #44ee22; width: 2; fill-color: #44ee22; fill-opacity: 0.2; set .area_small_name;} way[boundary] { color: #000066; width: 2; opacity: 0.6; dashes: 24,4, 4, 4; z-index: 4;} /* Perhaps should be filled, on lower zooms. */ diff --git a/resources/stylesheets/core_relations.css b/resources/stylesheets/core_relations.css index d1b2d5f6..7756abe6 100644 --- a/resources/stylesheets/core_relations.css +++ b/resources/stylesheets/core_relations.css @@ -1,7 +1,7 @@ /* Route relations */ -relation[type=route] way { z-index: 1; width: 13; color: blue; opacity: 0.3; } -relation[type=route][route=bicycle][network=ncn] way { z-index: 1; width: 12; color: red; opacity: 0.3; } -relation[type=route][route=bicycle][network=rcn] way { z-index: 1; width: 12; color: cyan; opacity: 0.3; } -relation[type=route][route=bicycle][network=lcn] way { z-index: 1; width: 12; color: blue; opacity: 0.3; } -relation[type=route][route=foot] way { z-index: 1; width: 10; color: #80ff80; opacity: 0.6; } +relation[type=route] way::route { z-index: 1; width: 13; color: blue; opacity: 0.3; } +relation[type=route][route=bicycle][network=ncn] way::route { z-index: 1; width: 12; color: red; opacity: 0.3; } +relation[type=route][route=bicycle][network=rcn] way::route { z-index: 1; width: 12; color: cyan; opacity: 0.3; } +relation[type=route][route=bicycle][network=lcn] way::route { z-index: 1; width: 12; color: blue; opacity: 0.3; } +relation[type=route][route=foot] way::route { z-index: 1; width: 10; color: #80ff80; opacity: 0.6; } diff --git a/resources/stylesheets/core_ways.css b/resources/stylesheets/core_ways.css index 9e087f59..b7c28233 100644 --- a/resources/stylesheets/core_ways.css +++ b/resources/stylesheets/core_ways.css @@ -7,23 +7,23 @@ way[highway=primary],way[highway=primary_link], way[highway=secondary],way[highway=secondary_link], way[highway=tertiary],way[highway=tertiary_link], way[highway=residential],way[highway=unclassified] { text: name; text-color: black; font-size: 10; text-position: line; text-halo-color: white; text-halo-radius: 2; } -way[highway=motorway],way[highway=motorway_link] { z-index: 9; color: #809BC0; width: 7; casing-color: black; casing-width: 8; } -way[highway=trunk],way[highway=trunk_link] { z-index: 9; color: #7FC97F; width: 7; casing-color: black; casing-width: 8; } -way[highway=primary],way[highway=primary_link] { z-index: 8; color: #E46D71; width: 7; casing-color: black; casing-width: 8; } -way[highway=secondary],way[highway=secondary_link] { z-index: 7; color: #FDBF6F; width: 7; casing-width: 8; } -way[highway=tertiary] { z-index: 6; color: #FEFECB; width: 5; casing-width: 7; } -way[highway=unclassified] { z-index: 6; color: #D0D0D0; width: 5; casing-width: 7; } -way[highway=tertiary_link] { z-index: 5; color: #FEFECB; width: 4; casing-width: 5; } -way[highway=residential] { z-index: 5; color: #E8E8E8; width: 5; casing-color: gray; casing-width: 7; } -way[highway=service][service!=parking_aisle] { color: white; width: 3; casing-color: gray; casing-width: 5; } -way[highway=service][service=parking_aisle] { color: white; width: 1; casing-color: #aaaaaa; casing-width: 2; } -way[highway=service][service=alley] { color: white; width: 2; dashes: 6,2; casing-color: black; casing-width: 4; } -way[highway=road] { color: gray; width: 5; casing-color: white; casing-width: 7; } -way[highway=living_street] { z-index: 5; color: #ddffee; width: 3; casing-color: #555555; casing-width: 4; } +way[highway=motorway],way[highway=motorway_link] { z-index: 9; color: #809BC0; width: 7; casing-color: black; casing-width: 1; } +way[highway=trunk],way[highway=trunk_link] { z-index: 9; color: #7FC97F; width: 7; casing-color: black; casing-width: 1; } +way[highway=primary],way[highway=primary_link] { z-index: 8; color: #E46D71; width: 7; casing-color: black; casing-width: 1; } +way[highway=secondary],way[highway=secondary_link] { z-index: 7; color: #FDBF6F; width: 7; casing-width: 1; } +way[highway=tertiary] { z-index: 6; color: #FEFECB; width: 5; casing-width: 1; } +way[highway=unclassified] { z-index: 6; color: #D0D0D0; width: 5; casing-width: 1; } +way[highway=tertiary_link] { z-index: 5; color: #FEFECB; width: 4; casing-width: 1; } +way[highway=residential] { z-index: 5; color: #E8E8E8; width: 5; casing-color: gray; casing-width: 1; } +way[highway=service][service!=parking_aisle] { color: white; width: 3; casing-color: gray; casing-width: 1; } +way[highway=service][service=parking_aisle] { color: white; width: 1; casing-color: #aaaaaa; casing-width: 1; } +way[highway=service][service=alley] { color: white; width: 2; dashes: 6,2; casing-color: black; casing-width: 1; } +way[highway=road] { color: gray; width: 5; casing-color: white; casing-width: 1; } +way[highway=living_street] { z-index: 5; color: #ddffee; width: 3; casing-color: #555555; casing-width: 1; } /* Road areas */ -way[highway=pedestrian] !:area { color: #ddddee; width: 5; casing-color: #555555; casing-width: 6; casing-dashes: 2,4;} +way[highway=pedestrian] !:area { color: #ddddee; width: 5; casing-color: #555555; casing-width: 1; casing-dashes: 2,4;} way[highway=pedestrian] :area { color: #555555; width: 1; fill-color: #ddddee; fill-opacity: 0.8; } /* Paths */ @@ -35,29 +35,28 @@ way[highway=bridleway] { z-index:9; color: #996644; width: 2; dashes: 4, 2, 2, 2 way[highway=track] { color: #996644; width: 2; dashes: 4, 2; set .path;} way[highway=path] { color: brown; width: 2; dashes: 2, 2; set .path;} way[highway=cycleway] { color: blue; width: 2; dashes: 4, 2; set .path;} -way[railway=tram] { z-index: 11; color: #999999; width: 2; casing-color: black; casing-width: 6; } +way[railway=tram] { z-index: 11; color: #999999; width: 2; casing-color: black; casing-width: 2; } way .path { text:name; text-color: black; text-position: offset; text-offset: 5;} /* Under construction */ way[highway=proposed] { color: #88ffff; width: 6; dashes: 8, 4; } -way[highway=construction] { color: #ffffbb; width: 6; dashes: 8, 4; casing-color: #0000aa; casing-width: 8; casing-dashes: 8,4;} -way[construction=rail] - { z-index: 6; color: black; width: 5; dashes: 6, 6, 4, 8;} - { z-index: 7; color: white; width: 3; dashes: 6,18; } +way[highway=construction] { color: #ffffbb; width: 6; dashes: 8, 4; casing-color: #0000aa; casing-width: 1; casing-dashes: 8,4;} +way[construction=rail] { z-index: 6; color: black; width: 5; dashes: 6, 6, 4, 8;} +way[construction=rail]::inner { z-index: 7; color: white; width: 3; dashes: 6,18; } /* Railways */ -way[railway=rail] - { z-index: 6; color: black; width: 5; } - { z-index: 7; color: white; width: 3; dashes: 12,12; } +way[railway=rail] { z-index: 6; color: black; width: 5; } +way[railway=rail]::dashes { z-index: 7; color: white; width: 3; dashes: 12,12; } + way[railway=platform] { color:black; width: 2; } -way[railway=subway] - { z-index: 6; color: #444444; width: 5; } - { z-index: 7; color: white; width: 3; dashes: 8,8; } -way[railway=disused],way[railway=abandoned] - { z-index: 6; color: #444400; width: 3; dashes: 17, 2, 5, 0; } - { z-index: 7; color: #999999; width: 2; dashes: 12,12; } + +way[railway=subway] { z-index: 6; color: #444444; width: 5; } +way[railway=subway]::dashes { z-index: 7; color: white; width: 3; dashes: 8,8; } + +way[railway=disused],way[railway=abandoned] { z-index: 6; color: #444400; width: 3; dashes: 17, 2, 5, 0; } +way[railway=disused]::dashes,way[railway=abandoned]::dashes { z-index: 7; color: #999999; width: 2; dashes: 12,12; } /* Waterways */ @@ -70,25 +69,26 @@ way[waterway][tunnel=yes] {dashes: 8,4;} /* Aeroways */ way[aeroway=aerodrome] :area - { z-index: 3; color: #bb44bb; width: 3; casing-color: #66066; casing-width: 4; } + { z-index: 3; color: #bb44bb; width: 3; casing-color: #66066; casing-width: 1; } way|z-15[aeroway=aerodrome] :area { z-index: 3; fill-color: #bb99bb; fill-opacity: 0.5;} -way[aeroway=taxiway] !:area { z-index: 8; color: #999999; width: 3; casing-color: #aa66aa; casing-width: 6; } +way[aeroway=taxiway] !:area { z-index: 8; color: #999999; width: 3; casing-color: #aa66aa; casing-width: 2; } way[aeroway=taxiway] :area { z-index: 8; color: #bb99bb; width: 3; fill-color: #ccaacc; } -way|z17-[aeroway=runway] !:area - { z-index: 9; color: black; width: 11; casing-color: #aa66aa; casing-width: 12; } - { z-index: 10; color: white; width: 9; dashes: 0, 20, 4, 76; } - { z-index: 11; color: black; width: 7; } - { z-index: 12; color: white; width: 5; dashes: 0, 20, 4, 76; } - { z-index: 13; color: black; width: 3; } - { z-index: 14; color: white; width: 1; dashes: 4, 16; } -way|z15-16[aeroway=runway] !:area - { z-index: 9; color: black; width: 5; } - { z-index: 12; color: white; width: 5; dashes: 0, 20, 4, 76; } - { z-index: 13; color: black; width: 3; } - { z-index: 14; color: white; width: 1; dashes: 4, 16; } -way|z-14[aeroway=runway] !:area - { z-index: 9; color: #444444; width: 3; } + +way|z17-[aeroway=runway] !:area { z-index: 9; color: black; width: 11; casing-color: #aa66aa; casing-width: 1; } +way|z17-[aeroway=runway]::aa !:area { z-index: 10; color: white; width: 9; dashes: 0, 20, 4, 76; } +way|z17-[aeroway=runway]::bb !:area { z-index: 11; color: black; width: 7; } +way|z17-[aeroway=runway]::cc !:area { z-index: 12; color: white; width: 5; dashes: 0, 20, 4, 76; } +way|z17-[aeroway=runway]::dd !:area { z-index: 13; color: black; width: 3; } +way|z17-[aeroway=runway]::ee !:area { z-index: 14; color: white; width: 1; dashes: 4, 16; } + +way|z15-16[aeroway=runway] !:area { z-index: 9; color: black; width: 5; } +way|z15-16[aeroway=runway]::aa !:area { z-index: 12; color: white; width: 5; dashes: 0, 20, 4, 76; } +way|z15-16[aeroway=runway]::bb !:area { z-index: 13; color: black; width: 3; } +way|z15-16[aeroway=runway]::cc !:area { z-index: 14; color: white; width: 1; dashes: 4, 16; } + +way|z-14[aeroway=runway] !:area { z-index: 9; color: #444444; width: 3; } + way[aeroway=runway] :area { z-index: 9; color: black; width: 3; fill-color: #775577; } way[aeroway=apron] :area { z-index: 4; color: #cc66cc; width: 1; fill-color: #ddaadd; fill-opacity: 0.5;} @@ -99,27 +99,25 @@ way[barrier=fence] {color: #000000; width: 1; dashes: 8,4,2,4; } /* Power */ -way[power=line] {color: darkgray; width: 3; dashes: 12,2; casing-color: black; casing-width: 8; casing-dashes: 4, 38;} -way[power=minor_line] {color: gray; width: 2; dashes: 2,4; casing-width: 8; casing-color: white; casing-dashes: 2,22;} +way[power=line] {color: darkgray; width: 3; dashes: 12,2; casing-color: black; casing-width: 2; casing-dashes: 4, 38;} +way[power=minor_line] {color: gray; width: 2; dashes: 2,4; casing-width: 3; casing-color: white; casing-dashes: 2,22;} way[power=station] :area { color: black; width: 2; fill-color: #666666; fill-opacity: 0.6; set .area_small_name;} way[power=generator] :area { color: black; width: 2; fill-color: #444444; fill-opacity: 0.6; set .area_small_name;} /* Leisure */ -way[golf=hole] {color: darkgreen; width: 5; casing-color: green; casing-width: 10; } +way[golf=hole] {color: darkgreen; width: 5; casing-color: green; casing-width: 2; } way[leisure=sports_centre] :area { color: #66ddcc; fill-color: #66ddcc; set .area_small_name; } /* Physical decoration */ -way[bridge=yes], way[bridge=viaduct], way[bridge=suspension] - { z-index: 4; color: white; width: eval('_width+3'); } - { z-index: 3; color: black; width: eval('_width+6'); } -way[tunnel=yes][!waterway] - { z-index: 4; color: white; width: eval('_width+2'); } - { z-index: 3; color: black; width: eval('_width+6'); dashes: 4,4; } +way[bridge=yes]::bridge1, way[bridge=viaduct]::bridge1, way[bridge=suspension]::bridge1 { z-index: 4; color: white; width: eval('_width+3'); } +way[bridge=yes]::bridge2, way[bridge=viaduct]::bridge2, way[bridge=suspension]::bridge2 { z-index: 3; color: black; width: eval('_width+6'); } +way[tunnel=yes][!waterway]::bridge1 { z-index: 4; color: white; width: eval('_width+2'); } +way[tunnel=yes][!waterway]::bridge2 { z-index: 3; color: black; width: eval('_width+6'); dashes: 4,4; } /* Attribute decoration */ -way[oneway=yes], way[junction=roundabout] { z-index: 15; color: #444444; width: 2; dashes: 15,35; line-style: arrows; } -way[oneway=-1] { z-index: 15; color: #444444; width: 2; dashes: 15,35; line-style: arrows-reversed; } +way[oneway=yes]::arrows, way[junction=roundabout]::arrows { z-index: 15; color: #444444; width: 2; dashes: 15,35; line-style: arrows; } +way[oneway=-1]::arrows { z-index: 15; color: #444444; width: 2; dashes: 15,35; line-style: arrows-reversed; } diff --git a/resources/stylesheets/enhanced.css b/resources/stylesheets/enhanced.css index 61315b39..23966e00 100644 --- a/resources/stylesheets/enhanced.css +++ b/resources/stylesheets/enhanced.css @@ -12,12 +12,12 @@ /* Access */ -way[access=private],way[access=no] { z-index: 10; color: red; width: eval('_width+2'); dashes: 2,5;} -way[access=permissive] { z-index: 10; color: green; width: eval('_width+2'); dashes: 1,3;} +way[access=private]::access,way[access=no]::access { z-index: 10; color: red; width: eval('_width+2'); dashes: 2,5;} +way[access=permissive]::access { z-index: 10; color: green; width: eval('_width+2'); dashes: 1,3;} /* Physical */ -way[embankment=yes], way[cutting=yes] +way[embankment=yes]::hatches, way[cutting=yes]::hatches { z-index: 3; opacity: 0.5; color: grey; width: eval('_width+5'); dashes: 2, 2; } /* Interactive behaviour */ @@ -28,8 +28,8 @@ way .area_small_name {text-color: black; font-size: 9; text: name; text-halo: #f /* Direction on selected ways */ -way[highway][!oneway][junction!=roundabout]:selected, -way[aerial_way]:selected { z-index: 14; color: #999922; width: 2; dashes: 3,60; line-style: arrows; } -way[waterway]:selected { z-index: 14; color: #4444CC; width: 2; dashes: 5,60; line-style: arrows; } -way[railway] :selected{ z-index: 14; color: #999999; width: 3; dashes: 4,92; line-style: arrows; } +way[highway][!oneway][junction!=roundabout]::direction :selected, +way[aerial_way]::direction :selected { z-index: 14; color: #999922; width: 2; dashes: 3,60; line-style: arrows; } +way[waterway]::direction :selected { z-index: 14; color: #4444CC; width: 2; dashes: 5,60; line-style: arrows; } +way[railway]::direction :selected{ z-index: 14; color: #999999; width: 3; dashes: 4,92; line-style: arrows; } diff --git a/resources/stylesheets/network.css b/resources/stylesheets/network.css index a8f72a6c..d2a60a9f 100644 --- a/resources/stylesheets/network.css +++ b/resources/stylesheets/network.css @@ -12,12 +12,12 @@ /* Access */ -way[access=private],way[access=no] { z-index: 10; color: red; width: eval('_width+2'); dashes: 2,5;} -way[access=permissive] { z-index: 10; color: green; width: eval('_width+2'); dashes: 1,3;} +way[access=private]::access,way[access=no]::access { z-index: 10; color: red; width: eval('_width+2'); dashes: 2,5;} +way[access=permissive]::access { z-index: 10; color: green; width: eval('_width+2'); dashes: 1,3;} /* Physical */ -way[embankment=yes], way[cutting=yes] +way[embankment=yes]::hatches, way[cutting=yes]::hatches { z-index: 3; opacity: 0.5; color: grey; width: eval('_width+5'); dashes: 2, 2; } /* Interactive behaviour */ @@ -28,8 +28,8 @@ way .area_small_name {text-color: black; font-size: 9; text: name; text-halo: #f /* Direction on selected ways */ -way[highway][!oneway][junction!=roundabout]:selected, -way[aerial_way]:selected { z-index: 14; color: #999922; width: 2; dashes: 3,60; line-style: arrows; } -way[waterway]:selected { z-index: 14; color: #4444CC; width: 2; dashes: 5,60; line-style: arrows; } -way[railway] :selected{ z-index: 14; color: #999999; width: 3; dashes: 4,92; line-style: arrows; } +way[highway][!oneway][junction!=roundabout]::direction :selected, +way[aerial_way]::direction :selected { z-index: 14; color: #999922; width: 2; dashes: 3,60; line-style: arrows; } +way[waterway]::direction :selected { z-index: 14; color: #4444CC; width: 2; dashes: 5,60; line-style: arrows; } +way[railway]::direction :selected{ z-index: 14; color: #999999; width: 3; dashes: 4,92; line-style: arrows; } diff --git a/resources/stylesheets/opencyclemap.css b/resources/stylesheets/opencyclemap.css index 194d9101..1270ba26 100644 --- a/resources/stylesheets/opencyclemap.css +++ b/resources/stylesheets/opencyclemap.css @@ -27,17 +27,17 @@ way[highway=primary],way[highway=primary_link], way[highway=secondary],way[highway=secondary_link], way[highway=tertiary],way[highway=tertiary_link], way[highway=residential] { text: name; text-color: black; font-size: 7; text-position: line;}*/ -way[highway=motorway],way[highway=motorway_link] { z-index: 9; color: #bfbfcf; width: 7; casing-color: #506077; casing-width: 9; } -way[highway=trunk],way[highway=trunk_link] { z-index: 9; color: #c8d8c8; width: 7; casing-color: #477147; casing-width: 9; } -way[highway=primary],way[highway=primary_link] { z-index: 8; color: #d8c8c8; width: 7; casing-color: #8d4346; casing-width: 9; } -way[highway=secondary],way[highway=secondary_link] { z-index: 7; color: #eeeec9; width: 7; casing-color: #a37b48; casing-width: 9; } -way[highway=tertiary],way[highway=unclassified] { z-index: 6; color: #eeeec9; width: 5; casing-color: #999999; casing-width: 7; } -way[highway=residential] { z-index: 5; color: white; width: 5; casing-color: #999; casing-width: 7; } -way[highway=service] { color: white; width: 3; casing-color: #999; casing-width: 5; } +way[highway=motorway],way[highway=motorway_link] { z-index: 9; color: #bfbfcf; width: 7; casing-color: #506077; casing-width: 1; } +way[highway=trunk],way[highway=trunk_link] { z-index: 9; color: #c8d8c8; width: 7; casing-color: #477147; casing-width: 1; } +way[highway=primary],way[highway=primary_link] { z-index: 8; color: #d8c8c8; width: 7; casing-color: #8d4346; casing-width: 1; } +way[highway=secondary],way[highway=secondary_link] { z-index: 7; color: #eeeec9; width: 7; casing-color: #a37b48; casing-width: 1; } +way[highway=tertiary],way[highway=unclassified] { z-index: 6; color: #eeeec9; width: 5; casing-color: #999999; casing-width: 1; } +way[highway=residential] { z-index: 5; color: white; width: 5; casing-color: #999; casing-width: 1; } +way[highway=service] { color: white; width: 3; casing-color: #999; casing-width: 1; } /* Pedestrian precincts need to be treated carefully. Only closed-loops with an explicit area=yes tag should be filled. The below doesn't yet work as intended. */ -way[highway=pedestrian] !:area { color: #ddddee; width: 5; casing-color: #555555; casing-width: 6; } +way[highway=pedestrian] !:area { color: #ddddee; width: 5; casing-color: #555555; casing-width: 1; } way[highway=pedestrian] :area { color: #555555; width: 1; fill-color: #ddddee; fill-opacity: 0.8; } way[highway=steps] { color: #be6c6c; width: 2; dashes: 4, 2; } @@ -107,26 +107,22 @@ node[barrier=cattle_grid] { icon-image: icons/cattle_grid.png; }*/ /* We can stack styles at different z-index (depth) */ -way[railway=rail] - { z-index: 6; color: #444444; width: 5; } - { z-index: 7; color: white; width: 3; dashes: 12,12; } +way[railway=rail] { z-index: 6; color: #444444; width: 5; } +way[railway=rail]::dashes { z-index: 7; color: white; width: 3; dashes: 12,12; } way[railway=platform] { color:black; width: 2; } -way[railway=subway] - { z-index: 6; color: #444444; width: 5; } - { z-index: 7; color: white; width: 3; dashes: 8,8; } +way[railway=subway] { z-index: 6; color: #444444; width: 5; } +way[railway=subway]::dashes { z-index: 7; color: white; width: 3; dashes: 8,8; } /* Bridge */ -way[bridge=yes], way[bridge=viaduct], way[bridge=suspension] - { z-index: 4; color: white; width: eval('_width+3'); } - { z-index: 3; color: black; width: eval('_width+6'); } +way[bridge=yes]::bridge1, way[bridge=viaduct]::bridge1, way[bridge=suspension]::bridge1 { z-index: 4; color: white; width: eval('_width+3'); } +way[bridge=yes]::bridge2, way[bridge=viaduct]::bridge2, way[bridge=suspension]::bridge2 { z-index: 3; color: black; width: eval('_width+6'); } /* Tunnel */ -way[tunnel=yes] - { z-index: 4; color: white; width: eval('_width+2'); } - { z-index: 3; color: black; width: eval('_width+6'); dashes: 4,4; } +way[tunnel=yes]::tunnel1 { z-index: 4; color: white; width: eval('_width+2'); } +way[tunnel=yes]::tunnel2 { z-index: 3; color: black; width: eval('_width+6'); dashes: 4,4; } /* Oneway */ -way[oneway=yes] { z-index: 10; color: #6c70d5; width: 2; dashes: 10,30; line-style: arrows; } +way[oneway=yes]::arrows { z-index: 10; color: #6c70d5; width: 2; dashes: 10,30; line-style: arrows; } /* Change the road colour based on dynamically set "highlighted" tag (see earlier) */ @@ -136,24 +132,16 @@ way .highlighted { color: pink; } /* Interactive editors may choose different behaviour when a user mouses-over or selects an object. Potlatch 2 supports these but the stand-alone Halcyon viewer does not */ -way :hover { z-index: 2; width: eval('_width+10'); color: #ffff99; } -way :selected { z-index: 2; width: eval('_width+10'); color: yellow; opacity: 0.7;} -way !:drawn { z-index:10; width: 0.5; color: gray; } +@import("stylesheets/core_interactive.css"); -node :selectedway { z-index: 9; icon-image: square; icon-width: 8; color: red; casing-color: #cc0000; casing-width: 1;} -node :hoverway { z-index: 9; icon-image: square; icon-width: 7; color: blue; } -node !:drawn :poi { z-index: 2; icon-image: circle; icon-width: 3; color: lightsteelblue; casing-color: black; casing-width: 1; } -node :selected { z-index: 1; icon-image: square; icon-width: eval('_width+10'); color: yellow; } -node :junction :selectedway { z-index: 8; icon-image: square; icon-width: 12; casing-color: black; casing-width: 1; } - /* Descendant selectors provide an easy way to style relations: this example means "any way which is part of a relation whose type=route". */ -relation[type=route] way { z-index: 1; width: 17; color: yellow; opacity: 0.3; } -relation[type=route][route=bicycle][network=ncn] way { z-index: 1; width: 12; color: red; opacity: 0.3; } -relation[type=route][route=bicycle][network=rcn] way { z-index: 1; width: 12; color: cyan; opacity: 0.3; } -relation[type=route][route=bicycle][network=lcn] way { z-index: 1; width: 12; color: blue; opacity: 0.3; } -relation[type=route][route=bicycle][network=mtb] way { z-index: 1; width: 12; color: #48a448; opacity: 0.3; } +relation[type=route] way::routeline { z-index: 1; width: 17; color: yellow; opacity: 0.3; } +relation[type=route][route=bicycle][network=ncn] way::routeline { z-index: 1; width: 12; color: red; opacity: 0.3; } +relation[type=route][route=bicycle][network=rcn] way::routeline { z-index: 1; width: 12; color: cyan; opacity: 0.3; } +relation[type=route][route=bicycle][network=lcn] way::routeline { z-index: 1; width: 12; color: blue; opacity: 0.3; } +relation[type=route][route=bicycle][network=mtb] way::routeline { z-index: 1; width: 12; color: #48a448; opacity: 0.3; } diff --git a/resources/stylesheets/potlatch.css b/resources/stylesheets/potlatch.css index 625d5462..34fd3bc4 100644 --- a/resources/stylesheets/potlatch.css +++ b/resources/stylesheets/potlatch.css @@ -18,3 +18,11 @@ way .area_small_name {text-color: black; font-size: 9; text: name; text-halo: #ffffaa; text-halo-radius: 2; text-position: center;} @import("stylesheets/core_interactive.css"); +/* Test rendering for licence status */ + +way[_status=no]::status { z-index: 0; width: 20; color: red; } +way[_status=partial]::status { z-index: 0; width: 20; color: red; opacity: 0.4; } +way[_status=unsure]::status { z-index: 0; width: 20; color: orange; opacity: 0.4; } +node[_status=no]::status { z-index: 0; icon-image: square; icon-width: 15; color: red; } +node[_status=partial]::status { z-index: 0; icon-image: square; icon-width: 15; color: red; opacity: 0.4; } +node[_status=unsure]::status { z-index: 0; icon-image: square; icon-width: 15; color: orange; opacity: 0.4; }