| 1 | #-*- encoding: utf-8 -*- |
|---|
| 2 | from math import * |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | # Some constants |
|---|
| 6 | X = 0 # Easting |
|---|
| 7 | Y = 1 # Northing |
|---|
| 8 | PHI = 0 # Latitude |
|---|
| 9 | LAMBDA = 1 # Longitude |
|---|
| 10 | EPS10 = 1.e-10 |
|---|
| 11 | HALFPI = pi / 2.0 |
|---|
| 12 | QUARTERPI = pi / 4.0 |
|---|
| 13 | |
|---|
| 14 | def gradesToRadians(grades): |
|---|
| 15 | return grades / 200.0 * pi |
|---|
| 16 | |
|---|
| 17 | |
|---|
| 18 | def sexagesimalToDecimal(degrees=0.0, minutes=0.0, seconds=0.0): |
|---|
| 19 | """ Convert a sexagesimal (degrees, minutes, seconds) to a decimal representation """ |
|---|
| 20 | return degrees + (minutes / 60.0) + (seconds / (60.0*60.0)) |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | def clampPositive(number): |
|---|
| 24 | """ Clamp negative numbers to 0.0 """ |
|---|
| 25 | if number < 0.0: |
|---|
| 26 | return 0.0 |
|---|
| 27 | else: |
|---|
| 28 | return number |
|---|
| 29 | |
|---|
| 30 | |
|---|
| 31 | class Ellipsoid: |
|---|
| 32 | """ An ellipsoid ; can be specified using semi minor axis length or inverse flattening """ |
|---|
| 33 | |
|---|
| 34 | def __init__(self, semiMajor, semiMinor=None, inverseFlattening=None): |
|---|
| 35 | self.semiMajor = semiMajor |
|---|
| 36 | self.semiMinor = semiMinor |
|---|
| 37 | # Some precalculated properties |
|---|
| 38 | if inverseFlattening is None: |
|---|
| 39 | self.inverseFlattening = self.semiMajor / (self.semiMajor - self.semiMinor) |
|---|
| 40 | else: |
|---|
| 41 | self.inverseFlattening = inverseFlattening |
|---|
| 42 | self.semiMinor = self.semiMajor - (self.semiMajor / self.inverseFlattening) |
|---|
| 43 | self.flattening = 1.0 / self.inverseFlattening |
|---|
| 44 | self.squaredExcentricity = 2.0 * self.flattening - self.flattening * self.flattening |
|---|
| 45 | self.excentricity = sqrt(self.squaredExcentricity) |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | STD_ELLIPSOIDS = { |
|---|
| 49 | "International 1924": Ellipsoid(6378388.0, inverseFlattening=297.0), |
|---|
| 50 | "Clarke 1866": Ellipsoid(6378206.400, inverseFlattening=294.97870), |
|---|
| 51 | "Clarke 1880 (IGN)": Ellipsoid(6378249.2, semiMinor=6356515.0), |
|---|
| 52 | "GRS 1980": Ellipsoid(6378137.0, inverseFlattening=298.257222101), |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | |
|---|
| 56 | class LambertProjectionParameters2SP: |
|---|
| 57 | """ Lambert conic conformal projection (2 standard parallels) parameters """ |
|---|
| 58 | |
|---|
| 59 | def __init__(self, ellipsoid, firstParallel, secondParallel, origin, shift): |
|---|
| 60 | self.ellipsoid = ellipsoid |
|---|
| 61 | self.firstParallel = firstParallel |
|---|
| 62 | self.secondParallel = secondParallel |
|---|
| 63 | self.origin = origin |
|---|
| 64 | self.shift = shift |
|---|
| 65 | |
|---|
| 66 | def __str__(self): |
|---|
| 67 | return "first parallel: %f\nsecond_parallel: %f" % \ |
|---|
| 68 | (degrees(self.firstParallel), degrees(self.secondParallel)) |
|---|
| 69 | |
|---|
| 70 | |
|---|
| 71 | def msfn(sinphi, cosphi, es): |
|---|
| 72 | return (cosphi / sqrt(1.0 - es * sinphi * sinphi)) |
|---|
| 73 | |
|---|
| 74 | |
|---|
| 75 | def tsfn(phi, sinphi, e): |
|---|
| 76 | sinphi *= e; |
|---|
| 77 | return (tan(0.5 * ((pi / 2.0) - phi)) / |
|---|
| 78 | pow((1. - sinphi) / (1. + sinphi), .5 * e)) |
|---|
| 79 | |
|---|
| 80 | |
|---|
| 81 | class LambertConicConformalProjection2SP: |
|---|
| 82 | """ Lambert conic conformal projection (2 standard parallels) """ |
|---|
| 83 | |
|---|
| 84 | _thetaAdjustment = 0.0 # Theta adjustment for belgium modified case |
|---|
| 85 | |
|---|
| 86 | def __init__(self, parameters): |
|---|
| 87 | self.setParameters(parameters) |
|---|
| 88 | |
|---|
| 89 | def setParameters(self, parameters): |
|---|
| 90 | self.parameters = parameters |
|---|
| 91 | projectionLatitude1 = parameters.firstParallel |
|---|
| 92 | projectionLatitude2 = parameters.secondParallel |
|---|
| 93 | projectionLatitude = parameters.origin[1] |
|---|
| 94 | e = self.parameters.ellipsoid.excentricity |
|---|
| 95 | es = self.parameters.ellipsoid.squaredExcentricity |
|---|
| 96 | self.n = sinphi = sin(projectionLatitude1) |
|---|
| 97 | cosphi = cos(projectionLatitude1) |
|---|
| 98 | secant = abs(projectionLatitude1 - projectionLatitude2) >= 1e-10 |
|---|
| 99 | spherical = (es == 0.0) |
|---|
| 100 | if not spherical: |
|---|
| 101 | m1 = msfn(sinphi, cosphi, es) |
|---|
| 102 | ml1 = tsfn(projectionLatitude1, sinphi, e) |
|---|
| 103 | if secant: |
|---|
| 104 | sinphi = sin(projectionLatitude2) |
|---|
| 105 | self.n = log(m1 / |
|---|
| 106 | msfn(sinphi, cos(projectionLatitude2), es)) |
|---|
| 107 | self.n /= log(ml1 / tsfn(projectionLatitude2, sinphi, e)) |
|---|
| 108 | self.c = self.rho0 = m1 * pow(ml1, -self.n) / self.n |
|---|
| 109 | if abs(abs(projectionLatitude) - HALFPI) < 1e-10: |
|---|
| 110 | self.rho0 *= 0.0 |
|---|
| 111 | else: |
|---|
| 112 | self.rho0 *= pow(tsfn(projectionLatitude, sin(projectionLatitude), e), self.n) |
|---|
| 113 | else: |
|---|
| 114 | if secant: |
|---|
| 115 | self.n = log(cosphi / cos(projectionLatitude2)) / \ |
|---|
| 116 | log(tan(QUARTERPI + .5 * projectionLatitude2) / \ |
|---|
| 117 | tan(QUARTERPI + .5 * projectionLatitude1)) |
|---|
| 118 | self.c = cosphi * pow(tan(QUARTERPI + .5 * projectionLatitude1), self.n) / self.n |
|---|
| 119 | if abs(abs(projectionLatitude) - HALFPI) < 1e-10: |
|---|
| 120 | self.rho0 = 0.0 |
|---|
| 121 | else: |
|---|
| 122 | self.rho0 = self.c * pow(tan(QUARTERPI + .5 * projectionLatitude), -n) |
|---|
| 123 | |
|---|
| 124 | def project(self, coordinates): |
|---|
| 125 | """ Projects geographical coordinates to (x, y) using Lambert projection. """ |
|---|
| 126 | coordinates = (coordinates[0] - self.parameters.origin[0], coordinates[1]) |
|---|
| 127 | if fabs(fabs(coordinates[1]) - (pi / 2.0)) < EPS10: |
|---|
| 128 | if coordinates[1] * self.n <= 0.0: |
|---|
| 129 | raise Exception |
|---|
| 130 | rho = 0.0 |
|---|
| 131 | else: |
|---|
| 132 | if self.parameters.ellipsoid.squaredExcentricity == 0.0: |
|---|
| 133 | rho = self.c * pow(tan((pi / 4.0) + 0.5 * coordinates[1]), -n) |
|---|
| 134 | else: |
|---|
| 135 | rho = self.c * pow(tsfn(coordinates[1], sin(coordinates[1]), self.parameters.ellipsoid.excentricity), self.n) |
|---|
| 136 | a = coordinates[0] * self.n |
|---|
| 137 | x = self.parameters.ellipsoid.semiMajor * (rho * sin(a)) + self.parameters.shift[0] |
|---|
| 138 | y = self.parameters.ellipsoid.semiMajor * (self.rho0 - rho * cos(a)) + self.parameters.shift[1] |
|---|
| 139 | return (x, y) |
|---|
| 140 | |
|---|
| 141 | |
|---|
| 142 | def inverse_project(self, coordinates): |
|---|
| 143 | """ Projects geographical coordinates to (x, y) using Lambert projection """ |
|---|
| 144 | |
|---|
| 145 | |
|---|
| 146 | class LambertConicConformalProjection2SPBelgium(LambertConicConformalProjection2SP): |
|---|
| 147 | """ Lambert conic conformal projection (2 standard parallels Belgium) """ |
|---|
| 148 | |
|---|
| 149 | _thetaAdjustment = radians(sexagesimalToDecimal(seconds=29.2985)) |
|---|
| 150 | |
|---|
| 151 | |
|---|
| 152 | STD_PROJECTIONS_PARAMETERS = { |
|---|
| 153 | "Lambert II Carto": LambertProjectionParameters2SP( |
|---|
| 154 | ellipsoid=STD_ELLIPSOIDS["Clarke 1880 (IGN)"], |
|---|
| 155 | firstParallel=radians(sexagesimalToDecimal(degrees=45.0, minutes=53.0, seconds=56.108)), # 45°53'56.108" |
|---|
| 156 | secondParallel=radians(sexagesimalToDecimal(degrees=47.0, minutes=41.0, seconds=45.652)), # 47°41'45.652" |
|---|
| 157 | origin=(radians(sexagesimalToDecimal(2.0, 20.0, 14.025)), radians(sexagesimalToDecimal(46.0, 48.0))), |
|---|
| 158 | shift=(600000.0, 2200000.0)), |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | STD_PROJECTIONS = { |
|---|
| 162 | "Lambert II Carto": LambertConicConformalProjection2SP(STD_PROJECTIONS_PARAMETERS["Lambert II Carto"]), |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | |
|---|
| 166 | def toDegrees(vector): |
|---|
| 167 | return [degrees(x) for x in vector] |
|---|
| 168 | |
|---|
| 169 | if __name__ == "__main__": |
|---|
| 170 | projection = STD_PROJECTIONS["Lambert II Carto"] |
|---|
| 171 | print projection.parameters |
|---|
| 172 | coords = (radians(5.82), radians(43.95)) |
|---|
| 173 | result = projection.project(coords) |
|---|
| 174 | #result = ((result[0] - 840005) / 16.666 , (result[1] - 1889995) / 16.666) |
|---|
| 175 | print "coords: %s,\nprojected: %s" % (coords, result) |
|---|
| 176 | #print "...", [degrees(x) for x in projection.unProject((840005, 1889995))] |
|---|