2 # -------------------------------------------------------------------------------------------------
3 # geo.Coord.new([<coord>]) ... class that holds and maintains geographical coordinates
4 # can be initialized with another geo.Coord instance
5 # Coord.set(<coord>) ... sets coordinates from another geo.Coord instance
7 # Coord.set_lat(<num>) ... functions for setting latitude/longitude/altitude
10 # Coord.set_latlon(<num>, <num> [, <num>]) (altitude is optional; default=0)
12 # Coord.set_x(<num>) ... functions for setting cartesian x/y/z coordinates
15 # Coord.set_xyz(<num>, <num>, <num>)
18 # Coord.lon() ... functions for getting lat/lon/alt
19 # Coord.alt() ... returns altitude in m
20 # Coord.latlon() ... returns array [<lat>, <lon>, <alt>]
22 # Coord.x() ... functions for reading cartesian coords (in m)
25 # Coord.xyz() ... returns array [<x>, <y>, <z>]
27 # Coord.course_to(<coord>) ... returns course to another geo.Coord instance (degree)
28 # Coord.distance_to(<coord>) ... returns distance in m along Earth curvature, ignoring altitudes
29 # useful for map distance
30 # Coord.direct_distance_to(<coord>) ... distance in m direct, considers altitude,
31 # but cuts through Earth surface
33 # Coord.apply_course_distance(<course>, <distance>) ... guess what
34 # Coord.dump() ... outputs coordinates
35 # Coord.is_defined() ... returns whether the coords are defined
37 # geo.aircraft_position() ... returns current aircraft position as geo.Coord
38 # geo.click_position() ... returns last click coords as geo.Coord or nil before first click
40 # geo.tile_path(<lat>, <lon>) ... returns tile path string (e.g. "w130n30/w123n37/942056.stg")
41 # geo.elevation(<lat>, <lon>) ... returns elevation in meter for given lat/lon, or nil on error
42 # geo.normdeg(<angle>) ... returns angle normalized to 0 <= angle < 360
44 # geo.put_model(<path>, <lat>, <lon> [, <elev:nil> [, <hdg:0> [, <pitch:0> [, <roll:0>]]]]);
45 # ... put model <path> at location <lat>/<lon> with given elevation
46 # (optional, default: surface). <hdg>/<pitch>/<roll> are optional
47 # and default to zero.
48 # geo.put_model(<path>, <coord> [, <hdg:0> [, <pitch:0> [, <roll:0>]]]);
49 # ... same as above, but lat/lon/elev are taken from a Coord object
52 var EPSILON = 0.0000000000001;
53 var ERAD = 6378138.12; # Earth radius (m)
54 var D2R = math.pi / 180;
55 var R2D = 180 / math.pi;
57 var M2FT = 3.28083989501312335958;
60 var printf = func { print(call(sprintf, arg)) }
61 var floor = func(v) { v < 0.0 ? -int(-v) - 1 : int(v) }
71 # class that maintains one set of geographical coordinates
74 new : func(copy = nil) {
75 var m = { parents: [Coord] };
76 m._pdirty = 1; # polar
77 m._cdirty = 1; # cartesian
78 m._lat = nil; # in radian
91 var xyz = geodtocart(me._lat * R2D, me._lon * R2D, me._alt);
99 var lla = carttogeod(me._x, me._y, me._z);
100 me._lat = lla[0] * D2R;
101 me._lon = lla[1] * D2R;
106 x : func { me._cupdate(); me._x },
107 y : func { me._cupdate(); me._y },
108 z : func { me._cupdate(); me._z },
109 xyz : func { me._cupdate(); [me._x, me._y, me._z] },
111 lat : func { me._pupdate(); me._lat * R2D }, # return in degree
112 lon : func { me._pupdate(); me._lon * R2D },
113 alt : func { me._pupdate(); me._alt },
114 latlon : func { me._pupdate(); [me._lat * R2D, me._lon * R2D, me._alt] },
116 set_x : func(x) { me._pupdate(); me._pdirty = 1; me._x = x; me },
117 set_y : func(y) { me._pupdate(); me._pdirty = 1; me._y = y; me },
118 set_z : func(z) { me._pupdate(); me._pdirty = 1; me._z = z; me },
120 set_lat : func(lat) { me._cupdate(); me._cdirty = 1; me._lat = lat * D2R; me },
121 set_lon : func(lon) { me._cupdate(); me._cdirty = 1; me._lon = lon * D2R; me },
122 set_alt : func(alt) { me._cupdate(); me._cdirty = 1; me._alt = alt; me },
133 set_latlon : func(lat, lon, alt = 0) {
141 set_xyz : func(x, y, z) {
149 apply_course_distance : func(course, dist) {
153 me._lat = asin(sin(me._lat) * cos(dist) + cos(me._lat) * sin(dist) * cos(course));
155 if (cos(me._lat) > EPSILON)
156 me._lon = math.pi - mod(math.pi - me._lon - asin(sin(course) * sin(dist)
157 / cos(me._lat)), 2 * math.pi);
162 course_to : func(dest) {
166 if (me._lat == dest._lat and me._lon == dest._lon)
169 var dlon = dest._lon - me._lon;
170 return mod(atan2(sin(dlon) * cos(dest._lat), cos(me._lat) * sin(dest._lat)
171 - sin(me._lat) * cos(dest._lat) * cos(dlon)), 2 * math.pi) * R2D;
173 # arc distance on an earth sphere; doesn't consider altitude
174 distance_to : func(dest) {
178 if (me._lat == dest._lat and me._lon == dest._lon)
181 var a = sin((me._lat - dest._lat) * 0.5);
182 var o = sin((me._lon - dest._lon) * 0.5);
183 return 2.0 * ERAD * asin(sqrt(a * a + cos(me._lat) * cos(dest._lat) * o * o));
185 direct_distance_to : func(dest) {
188 var dx = dest._x - me._x;
189 var dy = dest._y - me._y;
190 var dz = dest._z - me._z;
191 return sqrt(dx * dx + dy * dy + dz * dz);
194 return !(me._cdirty and me._pdirty);
197 if (me._cdirty and me._pdirty)
198 print("Coord.print(): coord undefined");
202 printf("x=%f y=%f z=%f lat=%f lon=%f alt=%f",
203 me.x(), me.y(), me.z(), me.lat(), me.lon(), me.alt());
208 # normalize degree to 0 <= angle < 360
210 var normdeg = func(angle) {
219 var bucket_span = func(lat) {
253 var tile_index = func(lat, lon) {
254 var lat_floor = floor(lat);
255 var lon_floor = floor(lon);
256 var span = bucket_span(lat);
259 if (span < 0.0000001) {
261 } elsif (span <= 1.0) {
262 x = int((lon - lon_floor) / span);
265 lon = int(int(lon / span) * span);
267 lon = int(int((lon + 1) / span) * span - span);
273 var y = int((lat - lat_floor) * 8);
274 return (lon_floor + 180) * 16384 + (lat_floor + 90) * 64 + y * 8 + x;
278 var format = func(lat, lon) {
279 sprintf("%s%03d%s%02d", lon < 0 ? "w" : "e", abs(lon), lat < 0 ? "s" : "n", abs(lat));
283 var tile_path = func(lat, lon) {
284 var p = format(floor(lat / 10.0) * 10, floor(lon / 10.0) * 10);
285 p ~= "/" ~ format(floor(lat), floor(lon));
286 p ~= "/" ~ tile_index(lat, lon) ~ ".stg";
290 var put_model = func(path, c, arg...) {
291 call(_put_model, [path] ~ (isa(c, Coord) ? c.latlon() : [c]) ~ arg);
295 var _put_model = func(path, lat, lon, elev_m = nil, hdg = 0, pitch = 0, roll = 0) {
297 elev_m = elevation(lat, lon);
299 die("can't get elevation for " ~ lat ~ "/" ~ lon);
300 var n = props.globals.getNode("/models");
301 for (var i = 0; 1; i += 1)
302 if (n.getChild("model", i, 0) == nil)
304 n = n.getChild("model", i, 1);
305 n.getNode("path", 1).setValue(path);
306 n.getNode("latitude-deg", 1).setDoubleValue(lat);
307 n.getNode("longitude-deg", 1).setDoubleValue(lon);
308 n.getNode("elevation-ft", 1).setDoubleValue(elev_m * M2FT);
309 n.getNode("heading-deg", 1).setDoubleValue(hdg);
310 n.getNode("pitch-deg", 1).setDoubleValue(pitch);
311 n.getNode("roll-deg", 1).setDoubleValue(roll);
312 n.getNode("load", 1).setBoolValue(1);
313 n.removeChildren("load");
318 var elevation = func(lat, lon) {
319 var d = geodinfo(lat, lon);
320 return d == nil ? nil : d[0];
324 var aircraft_lat = nil;
325 var aircraft_lon = nil;
326 var aircraft_alt = nil;
328 var aircraft_position = func {
329 var lat = aircraft_lat.getValue();
330 var lon = aircraft_lon.getValue();
331 var alt = aircraft_alt.getValue() * FT2M;
332 return Coord.new().set_latlon(lat, lon, alt);
338 var click_elev = nil;
339 var click_coord = Coord.new();
341 _setlistener("/sim/signals/click", func {
342 var lat = click_lat.getValue();
343 var lon = click_lon.getValue();
344 var elev = click_elev.getValue();
345 click_coord.set_latlon(lat, lon, elev);
348 var click_position = func {
349 return click_coord.is_defined() ? Coord.new(click_coord) : nil;
354 _setlistener("/sim/signals/nasal-dir-initialized", func {
363 aircraft_lat = props.globals.getNode("/position/latitude-deg", 1);
364 aircraft_lon = props.globals.getNode("/position/longitude-deg", 1);
365 aircraft_alt = props.globals.getNode("/position/altitude-ft", 1);
367 click_lat = props.globals.getNode("/sim/input/click/latitude-deg", 1);
368 click_lon = props.globals.getNode("/sim/input/click/longitude-deg", 1);
369 click_elev = props.globals.getNode("/sim/input/click/elevation-m", 1);