/* * © Copyright 2014 – Urheberrechtshinweis * Alle Rechte vorbehalten / All Rights Reserved * * Programmcode ist urheberrechtlich geschuetzt. * Das Urheberrecht liegt, soweit nicht ausdruecklich anders gekennzeichnet, bei Frank Ebner. * Keine Verwendung ohne explizite Genehmigung. * (vgl. § 106 ff UrhG / § 97 UrhG) */ #ifndef EARTHMAPPING_H #define EARTHMAPPING_H #include "Point3.h" #include "EarthPos.h" #include "../floorplan/v2/Floorplan.h" #include "../floorplan/v2/FloorplanHelper.h" #include "../misc/Debug.h" /** * mapping between positions on earth and positions within the floorplan */ class EarthMapping { private: /** one 3D position within the floorplan */ Point3 center_map_m; /** corresponding 3D position on earth */ EarthPos center_earth; /** rotation [in degrees] to ensure that the map's y-up-axis points towards the north */ float rotation_deg; double m_per_deg_lat; double m_per_deg_lon; static constexpr const char* NAME = "EarthMap"; public: /** ctor with parameters */ EarthMapping(Point3 center_map_m, EarthPos center_earth, float rotation_deg) : center_map_m(center_map_m), center_earth(center_earth), rotation_deg(rotation_deg) { precalc(); } /** ctor for a given map */ EarthMapping(Floorplan::IndoorMap* map) { // get the map<->earth correspondences from the floorplan const std::vector cor = map->earthReg.correspondences; // sanity check if (cor.size() < 3) { throw Exception("for EarthMapping to work, the map needs at least 3 correspondence points"); } Log::add(NAME, "calculating map<->earth correspondence using " + std::to_string(cor.size()) + " reference points"); // 1) // to reduce errors, use the average of all correspondces for earth<->map mapping Point3 _mapSum(0,0,0); EarthPos _earthSum(0,0,0); for (const Floorplan::EarthPosMapPos* epmp : cor) { _mapSum += epmp->posOnMap_m; _earthSum.lat += epmp->posOnEarth.lat; _earthSum.lon += epmp->posOnEarth.lon; _earthSum.height += epmp->posOnEarth.height; } const Point3 _mapAvg = _mapSum / cor.size(); const EarthPos _earthAvg = EarthPos(_earthSum.lat/cor.size(), _earthSum.lon/cor.size(), _earthSum.height/cor.size()); // 2) // initialize the mapper with those values // this allows a first mapping, but the map is not facing towards the north! rotation_deg = 0; // currently unkown center_map_m = _mapAvg; center_earth = _earthAvg; precalc(); Log::add(NAME, "avg. reference points: " + _mapAvg.asString() + " <-> " + _earthAvg.asString()); // 3) // now we use this initial setup to determine the rotation angle between map and world float deltaAngleSum = 0; for (Floorplan::EarthPosMapPos* epmp : cor) { // angle between mapAvg and mapCorrespondencePoint const float angleMap = std::atan2(_mapAvg.y - epmp->posOnMap_m.y, _mapAvg.x - epmp->posOnMap_m.x); // use the current setup to convert from map to world, WITHOUT correct rotation const Point3 repro = this->worldToMap(epmp->posOnEarth); // get the angle between mapAvg and projectedCorrespondencePoint const float angleEarth = std::atan2(_mapAvg.y - repro.y, _mapAvg.x - repro.x); // the difference between angleMap and angleEarth contains the angle needed to let the map face northwards // we use the average of all those angles determined by each correspondence const float dx_rad = angleEarth - angleMap; float dx_deg = (dx_rad * 180 / M_PI); if (dx_deg < 0) {dx_deg = 360 + dx_deg;} deltaAngleSum += dx_deg; } const float deltaSumAvg = deltaAngleSum / cor.size(); Log::add(NAME, "avg angular difference [north-rotation]: " + std::to_string(deltaSumAvg)); // 4) // the current center is not the rotation center we actually need: // e.g. when correspondence points were outside of the building, the rotation center is wrong // as real rotation center, we use the building's bbox center and the correspondence lon/lat on earth const BBox3 bbox = FloorplanHelper::getBBox(map); const Point2 _mapCenter2 = ((bbox.getMax() - bbox.getMin()) / 2).xy(); const Point3 _mapCenter3 = Point3(_mapCenter2.x, _mapCenter2.y, this->center_map_m.z); // keep original z! this->center_earth = mapToWorld(_mapCenter3); this->center_map_m = _mapCenter3; Log::add(NAME, "setting rotation center from bbox: " + center_map_m.asString() + " <-> " + center_earth.asString()); // 5) // finally, let the mapper know the north-angle this->rotation_deg = deltaSumAvg; } void build() { // TODO precalc(); } /** convert from map-coordinates to earth-coordinates */ EarthPos mapToWorld(const Point3 mapPos_m) const { // move to (0,0,0) Point3 pos = mapPos_m - center_map_m; // undo the rotation const Point2 xy = pos.xy().rotated(degToRad(+rotation_deg)); // convert this "delta to (0,0,0)" to lon/lat and move it to the lon/lat-center const double lat = center_earth.lat + (xy.y / m_per_deg_lat); const double lon = center_earth.lon + (xy.x / m_per_deg_lon); const float height = center_earth.height + pos.z; // done return EarthPos(lat, lon, height); } /** convert from earth-coordinates to map-coordinates */ Point3 worldToMap(const EarthPos earthPos) const { // move to center and scale const double y_m = +(earthPos.lat - center_earth.lat) * m_per_deg_lat; const double x_m = +(earthPos.lon - center_earth.lon) * m_per_deg_lon; const double z_m = (earthPos.height - center_earth.height); // rotate (our map is axis aligned) Point2 xy(x_m, y_m); xy = xy.rotated(degToRad(-rotation_deg)); // append height Point3 pos3(xy.x, xy.y, z_m); // move from center pos3 += center_map_m; return pos3; } private: /** perform some pre-calculations to speed things up */ void precalc() { const double refLat = center_earth.lat / 180.0 * M_PI; m_per_deg_lat = 111132.954 - 559.822 * std::cos( 2.0 * refLat ) + 1.175 * std::cos( 4.0 * refLat); m_per_deg_lon = 111132.954 * std::cos ( refLat ); } static inline float degToRad(const float deg) { return deg / 180.0f * (float) M_PI; } }; #endif // EARTHMAPPING_H