/// <summary> /// 中心地(経緯度)と東西軸方向・南北軸方向の距離(長さ次元)を含む矩形領域について /// DEMを切り出し・合成して取得する /// </summary> /// <remarks> /// 東西軸方向の臨界地点は赤道から遠い側(北半球なら北端における東西軸)が equatorial_delta を含む範囲で生成する。 /// </remarks> /// <param name="delta">計算により採用された center からの経緯度差を out</param> /// <param name="z"></param> /// <param name="center"></param> /// <param name="equatorial_delta">中心地点から含めたい東西軸方向の距離</param> /// <param name="axial_delta">中心地点から含めたい南北軸方向の距離。 null の場合は equatorial_delta と同じ値とみなす</param> /// <param name="dem_type">対象とするDEM群</param> /// <returns>標高群</returns> public static double[,] LoadDEM (out ILonLatSettable delta , byte z , ILonLatGettable center , Length equatorial_delta , Length axial_delta = null , DEMType dem_type = DEMType.DEM_5A_5B_10B ) { axial_delta = axial_delta ?? equatorial_delta; // 南北方向の delta 地点へ center を射影した pa を得る var pa_angle = center.Latitude >= PlaneAngle.Zero ? PlaneAngle.Zero : PlaneAngle.FromTurns(0.5); var pa = center.ProjectionTo(axial_delta, pa_angle); // 東西方向の delta 地点へ pa を射影した pae を得る var pae = pa.ProjectionTo(equatorial_delta, PlaneAngle.FromTurns(0.25)); var d = pae - center; // out delta = d; return(LoadDEM(z, center, d, dem_type)); }
/// <summary> /// Vincenty のアルゴリズムで経緯度 origin から距離 distance だけ角度 angle の方位へ射影した経緯度を計算するよ /// </summary> /// <param name="origin">基点とする位置</param> /// <param name="distance">射影先までの距離</param> /// <param name="angle">射影する方位。北=0deg, 西=+90deg, 東=-90deg, 南=180deg。 atans(y,x) 的には 北向き=+x, 西向き=+y の系</param> /// <param name="planet">惑星。null の場合は WGS84</param> /// <returns>射影先の経緯度</returns> public static LonLat ProjectionTo (this ILonLatGettable origin , Length distance , PlaneAngle angle , IGeometricalSpecificationGettable planet = null ) { planet = planet ?? GeometricalSpecification.Earth_WGS84; var SinAlphpa1 = angle.Sin(); var CosAlpha1 = angle.Cos(); var TanU1 = (1 - planet.Flattening) * origin.Latitude.Tan(); var CosU1 = 1 / Math.Sqrt(1 + TanU1 * TanU1); var SinU1 = TanU1 * CosU1; var Sigma1 = Math.Atan2(TanU1, CosAlpha1); var SinAlpha = CosU1 * SinAlphpa1; var CosSqAlpha = 1 - SinAlpha * SinAlpha; var ERSq = planet.EquatorialRadius._m * planet.EquatorialRadius._m; var ARSq = planet.AxialRadius._m * planet.AxialRadius._m; var uSq = CosSqAlpha * (ERSq - ARSq) / ARSq; var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); var Sigma = distance._m / (planet.AxialRadius._m * A); var SigmaP = 0.0; var SinSigma = 0.0; var CosSigma = 0.0; var Cos2SigmaM = 0.0; do { Cos2SigmaM = Math.Cos(2 * Sigma1 + Sigma); SinSigma = Math.Sin(Sigma); CosSigma = Math.Cos(Sigma); var DeltaSigma = B * SinSigma * (Cos2SigmaM + B / 4 * (CosSigma * (-1 + 2 * Cos2SigmaM * Cos2SigmaM) - B / 6 * Cos2SigmaM * (-3 + 4 * SinSigma * SinSigma) * (-3 + 4 * Cos2SigmaM * Cos2SigmaM))) ; SigmaP = Sigma; Sigma = distance._m / (planet.AxialRadius._m * A) + DeltaSigma; } while (Math.Abs(Sigma - SigmaP) > 1e-12); var tmp = SinU1 * SinSigma - CosU1 * CosSigma * CosAlpha1; var Phi2 = PlaneAngle.FromRadians(Math.Atan2(SinU1 * CosSigma + CosU1 * SinSigma * CosAlpha1, (1 - planet.Flattening) * Math.Sqrt(SinAlpha * SinAlpha + tmp * tmp))); var Labmda = Math.Atan2(SinSigma * SinAlphpa1, CosU1 * CosSigma - SinU1 * SinSigma * CosAlpha1); var C = planet.Flattening / 16 * CosSqAlpha * (4 + planet.Flattening * (4 - 3 * CosSqAlpha)); var L = Labmda - (1 - C) * planet.Flattening * SinAlpha * (Sigma + C * SinSigma * (Cos2SigmaM + C * CosSigma * (-1 + 2 * Cos2SigmaM * Cos2SigmaM))); var Lambda2 = PlaneAngle.FromRadians((origin.Longitude._rad + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI); // normalise to -180...+180 //var revAz = Math.Atan2( SinAlpha, -tmp ); return(new LonLat(Lambda2, Phi2)); }
/// <summary> /// 経緯度の位置 a, b の惑星を楕円体近似した場合の距離が /// 許容範囲 tolerance 以下か判定 /// </summary> /// <param name="a">経緯度の位置1つめ</param> /// <param name="b">経緯度の位置2つめ</param> /// <param name="tolerance">許容範囲</param> /// <param name="llda">経緯度の距離を計算するアルゴリズム</param> /// <param name="planet">楕円体近似した惑星の形状定義; null の場合は <see cref="GeometricalSpecification.Earth_WGS84"/> が採用されます </param> /// <returns>許容範囲以下なら true</returns> public static bool NearlyEqualsTo (this ILonLatGettable a , ILonLatGettable b , Length tolerance = null , LonLatDistanceAlgorithm llda = LonLatDistanceAlgorithm.Haversine , IGeometricalSpecificationGettable planet = null ) { return(a.DistanceTo(b, llda, planet) <= (tolerance ?? Length.Meter)); }
/// <summary> /// 経緯度の位置 a と b の緯度方向の距離を計算する /// </summary> /// <param name="a">経緯度の位置1つめ</param> /// <param name="b">経緯度の位置2つめ</param> /// <param name="llda">経緯度の距離を計算するアルゴリズム</param> /// <param name="planet">楕円体近似した惑星の形状定義; null の場合は <see cref="GeometricalSpecification.Earth_WGS84"/> が採用されます </param> /// <returns>緯度方向の距離</returns> public static Length LatitudeDistanceTo (this ILonLatGettable a , ILonLatGettable b , LonLatDistanceAlgorithm llda = LonLatDistanceAlgorithm.Haversine , IGeometricalSpecificationGettable planet = null ) { var aa = new LonLat(PlaneAngle.Zero, a.Latitude); var bb = new LonLat(PlaneAngle.Zero, b.Latitude); return(aa.DistanceTo(bb, llda, planet)); }
/// <summary> /// 経緯度の位置 a と b の中間の緯度における経度方向の距離を計算する /// </summary> /// <param name="a">経緯度の位置1つめ</param> /// <param name="b">経緯度の位置2つめ</param> /// <param name="llda">経緯度の距離を計算するアルゴリズム</param> /// <param name="planet">楕円体近似した惑星の形状定義; null の場合は <see cref="GeometricalSpecification.Earth_WGS84"/> が採用されます </param> /// <returns>経度方向の距離</returns> public static Length LongitudeDistanceTo (this ILonLatGettable a , ILonLatGettable b , LonLatDistanceAlgorithm llda = LonLatDistanceAlgorithm.Haversine , IGeometricalSpecificationGettable planet = null ) { var alat = (a.Latitude + b.Latitude) / 2.0; var aa = new LonLat(a.Longitude, alat); var bb = new LonLat(b.Longitude, alat); return(aa.DistanceTo(bb, llda, planet)); }
/// <summary> /// DistanceTo の Haversine アルゴリズム版の実装詳細 /// </summary> /// <param name="a">経緯度の位置1つめ</param> /// <param name="b">経緯度の位置2つめ</param> /// <param name="planet">楕円体近似した惑星の形状定義; null の場合は <see cref="GeometricalSpecification.Earth_WGS84"/> が採用されます </param> /// <returns>距離</returns> internal static Length DistanceToHaversine (this ILonLatGettable a , ILonLatGettable b , IGeometricalSpecificationGettable planet ) { var r = planet.EquatorialRadius; var dlat = b.Latitude - a.Latitude; var dlon = b.Longitude - a.Longitude; var p = (dlat / 2.0).Sin() * (dlat / 2.0).Sin() + a.Latitude.Cos() * b.Latitude.Cos() * (dlon / 2.0).Sin() * (dlon / 2.0).Sin() ; var q = 2.0 * Math.Atan2(Math.Sqrt(p), Math.Sqrt(1.0 - p)); return(r * q); }
/// <summary> /// 2つの経緯度 a, b について、 /// 楕円体近似した惑星の形状定義 planet 上における /// 惑星表面上の地点間の距離を長さ次元で計算します /// </summary> /// <param name="a">経緯度の位置1つめ</param> /// <param name="b">経緯度の位置2つめ</param> /// <param name="llda">経緯度の距離を計算するアルゴリズム</param> /// <param name="planet">楕円体近似した惑星の形状定義; null の場合は <see cref="GeometricalSpecification.Earth_WGS84"/> が採用されます </param> /// <returns>距離</returns> public static Length DistanceTo (this ILonLatGettable a , ILonLatGettable b , LonLatDistanceAlgorithm llda = LonLatDistanceAlgorithm.Haversine , IGeometricalSpecificationGettable planet = null ) { planet = planet ?? GeometricalSpecification.Earth_WGS84; switch (llda) { case LonLatDistanceAlgorithm.Haversine: return(a.DistanceToHaversine(b, planet)); case LonLatDistanceAlgorithm.Vincenty: return(a.DistanceToVincenty(b, planet)); default: throw new NotImplementedException(); } }
/// <summary> /// DistanceTo の Vincenty アルゴリズム版の実装詳細 /// </summary> /// <param name="a">経緯度の位置1つめ</param> /// <param name="b">経緯度の位置2つめ</param> /// <param name="planet">楕円体近似した惑星の形状定義; null の場合は <see cref="GeometricalSpecification.Earth_WGS84"/> が採用されます </param> /// <returns>距離</returns> internal static Length DistanceToVincenty (this ILonLatGettable a , ILonLatGettable b , IGeometricalSpecificationGettable planet ) { var L = b.Longitude - a.Longitude; var U1 = Math.Atan((1.0 - planet.Flattening) * a.Latitude.Tan()); var U2 = Math.Atan((1.0 - planet.Flattening) * b.Latitude.Tan()); var SinU1 = Math.Sin(U1); var CosU1 = Math.Cos(U1); var SinU2 = Math.Sin(U2); var CosU2 = Math.Cos(U2); var Lambda = L; var LambdaP = PlaneAngle.Zero; var IterationLimit = 256; var CosSqAlpha = 0.0; var Cos2SigmaM = 0.0; var CosSigma = 0.0; var SinSigma = 0.0; var Sigma = 0.0; do { var SinLambda = Lambda.Sin(); var CosLambda = Lambda.Cos(); SinSigma = Math.Sqrt ((CosU2 * SinLambda) * (CosU2 * SinLambda) + (CosU1 * SinU2 - SinU1 * CosU2 * CosLambda) * (CosU1 * SinU2 - SinU1 * CosU2 * CosLambda) ); if (SinSigma == 0) { return(Length.Zero); } CosSigma = SinU1 * SinU2 + CosU1 * CosU2 * CosLambda; Sigma = Math.Atan2(SinSigma, CosSigma); var SinAlpha = CosU1 * CosU2 * SinLambda / SinSigma; CosSqAlpha = 1 - SinAlpha * SinAlpha; Cos2SigmaM = CosSigma - 2 * SinU1 * SinU2 / CosSqAlpha; if (double.IsNaN(Cos2SigmaM)) { Cos2SigmaM = 0; } var C = planet.Flattening / 16.0 * CosSqAlpha * (4.0 + planet.Flattening * (4.0 - 3.0 * CosSqAlpha)); LambdaP = Lambda; Lambda = L + PlaneAngle.FromRadians(1.0 - C) * planet.Flattening * SinAlpha * (Sigma + C * SinSigma * (Cos2SigmaM + C * CosSigma * (-1.0 + 2.0 * Cos2SigmaM * Cos2SigmaM ) ) ) ; }while (!Lambda.NearlyEquals(LambdaP, PlaneAngle.FromRadians(0.5e-12)) && --IterationLimit > 0 ); if (IterationLimit == 0) { return(Length.NaN); } var ERSq = planet.EquatorialRadius._m * planet.EquatorialRadius._m; var ARSq = planet.AxialRadius._m * planet.AxialRadius._m; var uSq = CosSqAlpha * (ERSq - ARSq) / ARSq; var A = 1.0 + uSq / 16384.0 * (4096.0 + uSq * (-768.0 + uSq * (320.0 - 175.0 * uSq))); var B = uSq / 1024.0 * (256.0 + uSq * (-128.0 + uSq * (74.0 - 47.0 * uSq))); var DeltaSigma = B * SinSigma * (Cos2SigmaM + B / 4.0 * (CosSigma * (-1 + 2 * Cos2SigmaM * Cos2SigmaM) - B / 6 * Cos2SigmaM * (-3 + 4 * SinSigma * SinSigma) * (-3 + 4 * Cos2SigmaM * Cos2SigmaM) ) ) ; return(Length.From_m(planet.AxialRadius._m * A * (Sigma - DeltaSigma))); }
/// <summary> /// 経緯度を取得可能な何かから生成 /// </summary> /// <param name="lonlat">ILonLatGettable な何か</param> /// <see cref="ILonLatGettable"/> /// <seealso cref="GeoURI"/> /// <remarks>LonLat からのコピーコンストラクターとしても機能する</remarks> public LonLat(ILonLatGettable lonlat) { Longitude = lonlat.Longitude; Latitude = lonlat.Latitude; }
/// <summary> /// 中心地(経緯度)と中心地からの範囲(経緯度差)による矩形領域について /// DEM を切り出し・合成して取得する /// </summary> /// <param name="z">Z</param> /// <param name="center">中心地</param> /// <param name="delta">中心地からの範囲(経緯度差)</param> /// <param name="dem_type">対象とするDEM群</param> /// <returns>標高群</returns> public static double[,] LoadDEM (byte z , ILonLatGettable center , ILonLatGettable delta , DEMType dem_type = DEMType.DEM_5A_5B_10B ) { // 緯度軸方向の北端と南端を確定 var dlatabs = delta.Latitude.Abs(); var north_angle = (center.Latitude + dlatabs).Normalize180(); var south_angle = (center.Latitude - dlatabs).Normalize180(); // 経度軸方向の西端と東端を確定 var dlonabs = delta.Longitude.Abs(); var east_angle = (center.Longitude + dlonabs).Normalize360(); var west_angle = (center.Longitude - dlonabs).Normalize360(); //Console.WriteLine( $"e={east_angle}" ); //Console.WriteLine( $"w={west_angle}" ); //Console.WriteLine( $"n={north_angle}" ); //Console.WriteLine( $"s={south_angle}" ); // 東端・北端のピクセルを確定 var north_east_pixel = WebMercator.AngleToPixel(new LonLat(east_angle, north_angle), z); // 西端・南端のピクセルを確定 var south_west_pixel = WebMercator.AngleToPixel(new LonLat(west_angle, south_angle), z); //Console.WriteLine( $"N-E px = {north_east_pixel}" ); //Console.WriteLine( $"S-W px = {south_west_pixel}" ); // 東端・北端のタイルを確定 var north_east_tile = WebMercator.PixelToTile(north_east_pixel); // 西端・南端のタイルを確定 var south_west_tile = WebMercator.PixelToTile(south_west_pixel); //Console.WriteLine( $"N-E tile = {north_east_tile}" ); //Console.WriteLine( $"S-W tile = {south_west_tile}" ); // 最終的に必要なピクセル群のX軸サイズ; 区間 [ west ... east ] なのでサイズは +1 // west < east var target_pixel_size_x = ( int )(north_east_pixel.X - south_west_pixel.X + 1); // 最終的に必要なピクセル群のY軸サイズ; 区間 [ north ... south ] なのでサイズは +1 // north < south var target_pixel_size_y = ( int )(south_west_pixel.Y - north_east_pixel.Y + 1); // 出力用のバッファー var r = new double[target_pixel_size_x, target_pixel_size_y]; // 北西から南東へ { // r 開始地点と data 開始地点のずれを計算 const int tile_pixel_shift = 8; const int data_arris_size = 1 << tile_pixel_shift; var dpx = (int)(south_west_tile.X << tile_pixel_shift) - (int)south_west_pixel.X; var dpy = (int)(north_east_tile.Y << tile_pixel_shift) - (int)north_east_pixel.Y; var t = new TileLocation(0, 0, z); // data ごとの r 開始位置を dp だけずらしておく var rx_begin = dpx; var ry_begin = dpy; for (t.Y = north_east_tile.Y ; t.Y <= south_west_tile.Y ; ++t.Y, ry_begin += data_arris_size, rx_begin = dpx ) { for (t.X = south_west_tile.X ; t.X <= north_east_tile.X ; ++t.X, rx_begin += data_arris_size ) { // デコードして var data = LoadDEM(t, dem_type); //using ( var i = SavePNGNumberTileS( data, 1.0e-2, 0, Color.FromArgb( 255, 128, 0, 0 ) ) ) // i.Save( System.IO.Path.Combine( System.Environment.GetFolderPath( System.Environment.SpecialFolder.DesktopDirectory ), $"GK/tmp/gyoe-{t.X}-{t.Y}-{t.Z}.png" ), System.Drawing.Imaging.ImageFormat.Png ); Func <int, int, double> sampler = (dx, dy) => data[dx, dy]; if (data == null) { sampler = (dx, dy) => double.NaN; } // r の適切な領域へ data をコピー var rx = rx_begin; var ry = ry_begin; //Console.WriteLine( $"t={t} rx_begin={rx_begin} ry_begin={ry_begin}" ); for (var dy = 0; dy < data_arris_size; ++dy, ++ry, rx = rx_begin) { if (ry < 0 || ry >= r.GetLength(1)) { continue; } else { for (var dx = 0; dx < data_arris_size; ++dx, ++rx) { if (rx < 0 || rx >= r.GetLength(0)) { continue; } else { //{ r[rx, ry] = sampler(dx, dy); } } } } //Console.WriteLine( $"rx={rx} ry={ry} dx={dx} dy={dy} value={data[ dx, dy ]}" ); //} } } } return(r); }