/** * \file PolygonArea.cpp * \brief Implementation for GeographicLib::PolygonAreaT class * * Copyright (c) Charles Karney (2010-2022) and licensed * under the MIT/X11 License. For more information, see * https://geographiclib.sourceforge.io/ **********************************************************************/ #include #if defined(_MSC_VER) // Squelch warnings about enum-float expressions # pragma warning (disable: 5055) #endif namespace GeographicLib { using namespace std; template int PolygonAreaT::transit(real lon1, real lon2) { // Return 1 or -1 if crossing prime meridian in east or west direction. // Otherwise return zero. longitude = +/-0 considered to be positive. // This is (should be?) compatible with transitdirect which computes // exactly the parity of // int(floor((lon1 + lon12) / 360)) - int(floor(lon1 / 360))) real lon12 = Math::AngDiff(lon1, lon2); lon1 = Math::AngNormalize(lon1); lon2 = Math::AngNormalize(lon2); // N.B. lon12 == 0 gives cross = 0 return // edge case lon1 = 180, lon2 = 360->0, lon12 = 180 to give 1 lon12 > 0 && ((lon1 < 0 && lon2 >= 0) || // lon12 > 0 && lon1 > 0 && lon2 == 0 implies lon1 == 180 (lon1 > 0 && lon2 == 0)) ? 1 : // non edge case lon1 = -180, lon2 = -360->-0, lon12 = -180 (lon12 < 0 && lon1 >= 0 && lon2 < 0 ? -1 : 0); // This was the old method (treating +/- 0 as negative). However, with the // new scheme for handling longitude differences this fails on: // lon1 = -180, lon2 = -360->-0, lon12 = -180 gives 0 not -1. // return // lon1 <= 0 && lon2 > 0 && lon12 > 0 ? 1 : // (lon2 <= 0 && lon1 > 0 && lon12 < 0 ? -1 : 0); } // an alternate version of transit to deal with longitudes in the direct // problem. template int PolygonAreaT::transitdirect(real lon1, real lon2) { // Compute exactly the parity of // int(floor(lon2 / 360)) - int(floor(lon1 / 360)) using std::remainder; // C++ C remainder -> [-360, 360] // Java % -> (-720, 720) switch to IEEEremainder -> [-360, 360] // JS % -> (-720, 720) // Python fmod -> (-720, 720) swith to Math.remainder // Fortran, Octave skip // If mod function gives result in [-360, 360] // [0, 360) -> 0; [-360, 0) or 360 -> 1 // If mod function gives result in (-720, 720) // [0, 360) or [-inf, -360) -> 0; [-360, 0) or [360, inf) -> 1 lon1 = remainder(lon1, real(2 * Math::td)); lon2 = remainder(lon2, real(2 * Math::td)); return ( (lon2 >= 0 && lon2 < Math::td ? 0 : 1) - (lon1 >= 0 && lon1 < Math::td ? 0 : 1) ); } template void PolygonAreaT::AddPoint(real lat, real lon) { if (_num == 0) { _lat0 = _lat1 = lat; _lon0 = _lon1 = lon; } else { real s12, S12, t; _earth.GenInverse(_lat1, _lon1, lat, lon, _mask, s12, t, t, t, t, t, S12); _perimetersum += s12; if (!_polyline) { _areasum += S12; _crossings += transit(_lon1, lon); } _lat1 = lat; _lon1 = lon; } ++_num; } template void PolygonAreaT::AddEdge(real azi, real s) { if (_num) { // Do nothing if _num is zero real lat, lon, S12, t; _earth.GenDirect(_lat1, _lon1, azi, false, s, _mask, lat, lon, t, t, t, t, t, S12); _perimetersum += s; if (!_polyline) { _areasum += S12; _crossings += transitdirect(_lon1, lon); } _lat1 = lat; _lon1 = lon; ++_num; } } template unsigned PolygonAreaT::Compute(bool reverse, bool sign, real& perimeter, real& area) const { real s12, S12, t; if (_num < 2) { perimeter = 0; if (!_polyline) area = 0; return _num; } if (_polyline) { perimeter = _perimetersum(); return _num; } _earth.GenInverse(_lat1, _lon1, _lat0, _lon0, _mask, s12, t, t, t, t, t, S12); perimeter = _perimetersum(s12); Accumulator<> tempsum(_areasum); tempsum += S12; int crossings = _crossings + transit(_lon1, _lon0); AreaReduce(tempsum, crossings, reverse, sign); area = real(0) + tempsum(); return _num; } template unsigned PolygonAreaT::TestPoint(real lat, real lon, bool reverse, bool sign, real& perimeter, real& area) const { if (_num == 0) { perimeter = 0; if (!_polyline) area = 0; return 1; } perimeter = _perimetersum(); real tempsum = _polyline ? 0 : _areasum(); int crossings = _crossings; unsigned num = _num + 1; for (int i = 0; i < (_polyline ? 1 : 2); ++i) { real s12, S12, t; _earth.GenInverse(i == 0 ? _lat1 : lat, i == 0 ? _lon1 : lon, i != 0 ? _lat0 : lat, i != 0 ? _lon0 : lon, _mask, s12, t, t, t, t, t, S12); perimeter += s12; if (!_polyline) { tempsum += S12; crossings += transit(i == 0 ? _lon1 : lon, i != 0 ? _lon0 : lon); } } if (_polyline) return num; AreaReduce(tempsum, crossings, reverse, sign); area = real(0) + tempsum; return num; } template unsigned PolygonAreaT::TestEdge(real azi, real s, bool reverse, bool sign, real& perimeter, real& area) const { if (_num == 0) { // we don't have a starting point! perimeter = Math::NaN(); if (!_polyline) area = Math::NaN(); return 0; } unsigned num = _num + 1; perimeter = _perimetersum() + s; if (_polyline) return num; real tempsum = _areasum(); int crossings = _crossings; { real lat, lon, s12, S12, t; _earth.GenDirect(_lat1, _lon1, azi, false, s, _mask, lat, lon, t, t, t, t, t, S12); tempsum += S12; crossings += transitdirect(_lon1, lon); _earth.GenInverse(lat, lon, _lat0, _lon0, _mask, s12, t, t, t, t, t, S12); perimeter += s12; tempsum += S12; crossings += transit(lon, _lon0); } AreaReduce(tempsum, crossings, reverse, sign); area = real(0) + tempsum; return num; } template template void PolygonAreaT::AreaReduce(T& area, int crossings, bool reverse, bool sign) const { Remainder(area); if (crossings & 1) area += (area < 0 ? 1 : -1) * _area0/2; // area is with the clockwise sense. If !reverse convert to // counter-clockwise convention. if (!reverse) area *= -1; // If sign put area in (-_area0/2, _area0/2], else put area in [0, _area0) if (sign) { if (area > _area0/2) area -= _area0; else if (area <= -_area0/2) area += _area0; } else { if (area >= _area0) area -= _area0; else if (area < 0) area += _area0; } } template class GEOGRAPHICLIB_EXPORT PolygonAreaT; template class GEOGRAPHICLIB_EXPORT PolygonAreaT; template class GEOGRAPHICLIB_EXPORT PolygonAreaT; } // namespace GeographicLib