From 3ec494ca69b85264d87b5680e4e91209b606f7a7 Mon Sep 17 00:00:00 2001 From: tabidachinokaze Date: Wed, 26 Nov 2025 18:58:15 +0800 Subject: [PATCH] initial commit --- .github/ISSUE_TEMPLATE/bug_report.yml | 38 + .github/ISSUE_TEMPLATE/feature_request.yml | 30 + .github/workflows/build.yml | 53 + .github/workflows/generate-screenshots.yml | 69 + .github/workflows/pick-me.yml | 13 + .github/workflows/release-apidocs.yml | 65 + .../release-candidate-to-maven-central.yml | 25 + .../workflows/release-to-maven-central.yml | 27 + .gitignore | 11 + android/.gitignore | 1 + android/build.gradle.kts | 62 + android/proguard-rules.pro | 21 + .../geotools/ExampleInstrumentedTest.kt | 24 + android/src/main/AndroidManifest.xml | 25 + .../com/icegps/geotools/ContoursManager.kt | 427 +++ .../com/icegps/geotools/ControllableArrow.kt | 197 ++ .../icegps/geotools/CoordinateGenerator.kt | 53 + .../com/icegps/geotools/DisplaySlopeResult.kt | 144 + .../com/icegps/geotools/EarthworkManager.kt | 438 +++ .../java/com/icegps/geotools/GridDisplay.kt | 83 + .../java/com/icegps/geotools/GridModel.kt | 139 + .../java/com/icegps/geotools/MainActivity.kt | 265 ++ .../java/com/icegps/geotools/MainViewModel.kt | 59 + .../com/icegps/geotools/PolylineManager.kt | 121 + .../icegps/geotools/RayCastingAlgorithm.kt | 44 + .../java/com/icegps/geotools/SimplePalette.kt | 123 + .../icegps/geotools/catmullrom/CatmullRom.kt | 135 + .../com/icegps/geotools/color/ColorRGBa.kt | 427 +++ .../geotools/colorbrewer2/ColorBrewer2.kt | 2777 +++++++++++++++++ .../java/com/icegps/geotools/ktx/ColorRGBa.kt | 23 + .../java/com/icegps/geotools/ktx/Context.kt | 12 + .../java/com/icegps/geotools/ktx/Vector2D.kt | 24 + .../java/com/icegps/geotools/ktx/Vector3D.kt | 32 + .../marchingsquares/MarchingSquares.kt | 219 ++ .../res/drawable/ic_launcher_background.xml | 170 + .../res/drawable/ic_launcher_foreground.xml | 30 + .../main/res/layout-port/activity_main.xml | 190 ++ android/src/main/res/layout/activity_main.xml | 186 ++ .../main/res/mipmap-anydpi/ic_launcher.xml | 6 + .../res/mipmap-anydpi/ic_launcher_round.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes android/src/main/res/values-night/themes.xml | 7 + android/src/main/res/values/colors.xml | 5 + .../main/res/values/mapbox_access_token.xml | 4 + android/src/main/res/values/strings.xml | 3 + android/src/main/res/values/themes.xml | 9 + .../com/icegps/geotools/ExampleUnitTest.kt | 17 + .../geotools/TriangulationToGridTest.kt | 71 + build.gradle | 6 + gradle.properties | 23 + gradle/libs.versions.toml | 42 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45633 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 248 ++ gradlew.bat | 93 + icegps-common/.gitignore | 1 + icegps-common/build.gradle.kts | 44 + icegps-common/consumer-rules.pro | 0 icegps-common/proguard-rules.pro | 21 + .../icegps/common/ExampleInstrumentedTest.kt | 24 + icegps-common/src/main/AndroidManifest.xml | 4 + .../java/com/icegps/common/helper/BlhToEnu.kt | 274 ++ .../com/icegps/common/helper/GeoHelper.kt | 419 +++ .../java/com/icegps/common/ExampleUnitTest.kt | 17 + icegps-shared/.gitignore | 1 + icegps-shared/build.gradle.kts | 57 + icegps-shared/consumer-rules.pro | 0 icegps-shared/proguard-rules.pro | 21 + .../icegps/shared/ExampleInstrumentedTest.kt | 24 + icegps-shared/src/main/AndroidManifest.xml | 4 + .../com/icegps/shared/SharedHttpClient.kt | 42 + .../main/java/com/icegps/shared/SharedJson.kt | 17 + .../com/icegps/shared/api/LookupResponse.kt | 21 + .../com/icegps/shared/api/OpenElevationApi.kt | 35 + .../main/java/com/icegps/shared/ktx/Any.kt | 7 + .../java/com/icegps/shared/model/GeoPoint.kt | 11 + .../java/com/icegps/shared/model/IGeoPoint.kt | 11 + .../java/com/icegps/shared/ExampleUnitTest.kt | 17 + icegps-triangulation/.gitignore | 1 + icegps-triangulation/build.gradle.kts | 16 + .../com/icegps/triangulation/Delaunator.kt | 596 ++++ .../java/com/icegps/triangulation/Delaunay.kt | 225 ++ .../triangulation/DelaunayTriangulation.kt | 69 + .../com/icegps/triangulation/DoubleDouble.kt | 340 ++ .../com/icegps/triangulation/Predicates.kt | 19 + .../java/com/icegps/triangulation/Triangle.kt | 13 + math/.gitignore | 1 + math/build.gradle.kts | 13 + .../main/java/com/icegps/io/util/NumberExt.kt | 75 + .../java/com/icegps/io/util/NumberParser.kt | 78 + .../main/java/com/icegps/math/Alignment.kt | 47 + .../java/com/icegps/math/BooleanConversion.kt | 9 + math/src/main/java/com/icegps/math/Clamp.kt | 38 + .../main/java/com/icegps/math/ConvertRange.kt | 24 + .../src/main/java/com/icegps/math/Division.kt | 19 + math/src/main/java/com/icegps/math/Fract.kt | 4 + math/src/main/java/com/icegps/math/ILog.kt | 9 + .../java/com/icegps/math/IsAlmostEquals.kt | 14 + .../main/java/com/icegps/math/IsAlmostZero.kt | 4 + math/src/main/java/com/icegps/math/IsEven.kt | 9 + .../java/com/icegps/math/IsNanOrInfinite.kt | 11 + math/src/main/java/com/icegps/math/Math.kt | 133 + .../java/com/icegps/math/NormalizeZero.kt | 7 + .../main/java/com/icegps/math/PowerOfTwo.kt | 22 + .../com/icegps/math/RoundDecimalPlaces.kt | 16 + .../com/icegps/math/ToIntegerConverters.kt | 29 + math/src/main/java/com/icegps/math/Umod.kt | 35 + .../src/main/java/com/icegps/math/Unsigned.kt | 13 + .../math/annotations/_Math_annotations.kt | 41 + .../java/com/icegps/math/geometry/AABB3D.kt | 55 + .../java/com/icegps/math/geometry/Anchor.kt | 103 + .../java/com/icegps/math/geometry/Angle.kt | 250 ++ .../com/icegps/math/geometry/BoundsBuilder.kt | 60 + .../java/com/icegps/math/geometry/Circle.kt | 29 + .../java/com/icegps/math/geometry/Ellipse.kt | 83 + .../com/icegps/math/geometry/EulerRotation.kt | 330 ++ .../com/icegps/math/geometry/IPointList.kt | 124 + .../java/com/icegps/math/geometry/Line.kt | 175 ++ .../java/com/icegps/math/geometry/Line3D.kt | 3 + .../java/com/icegps/math/geometry/Margin.kt | 75 + .../java/com/icegps/math/geometry/Matrix.kt | 415 +++ .../java/com/icegps/math/geometry/Matrix3.kt | 237 ++ .../java/com/icegps/math/geometry/Matrix4.kt | 684 ++++ .../com/icegps/math/geometry/Matrix4Ext.kt | 9 + .../com/icegps/math/geometry/MatrixExt.kt | 64 + .../icegps/math/geometry/MatrixMajorOrder.kt | 3 + .../com/icegps/math/geometry/Orientation.kt | 55 + .../java/com/icegps/math/geometry/Polygon.kt | 3 + .../java/com/icegps/math/geometry/Polyline.kt | 3 + .../com/icegps/math/geometry/Quaternion.kt | 326 ++ .../main/java/com/icegps/math/geometry/Ray.kt | 90 + .../com/icegps/math/geometry/RectCorners.kt | 28 + .../com/icegps/math/geometry/Rectangle.kt | 291 ++ .../com/icegps/math/geometry/RectangleInt.kt | 89 + .../icegps/math/geometry/RoundRectangle.kt | 18 + .../java/com/icegps/math/geometry/Scale.kt | 52 + .../com/icegps/math/geometry/ScaleMode.kt | 40 + .../java/com/icegps/math/geometry/Size.kt | 150 + .../java/com/icegps/math/geometry/Spacing.kt | 28 + .../java/com/icegps/math/geometry/Sphere3D.kt | 13 + .../com/icegps/math/geometry/VectorExt.kt | 47 + .../com/icegps/math/geometry/VectorsDouble.kt | 352 +++ .../com/icegps/math/geometry/VectorsFloat.kt | 523 ++++ .../com/icegps/math/geometry/VectorsInt.kt | 41 + .../math/geometry/shape/SimpleShape2D.kt | 15 + .../math/geometry/shape/SimpleShape3D.kt | 8 + .../com/icegps/math/interpolation/Easing.kt | 44 + .../interpolation/Interpolation.vector.kt | 8 + .../com/icegps/math/interpolation/Ratio.kt | 109 + .../java/com/icegps/math/range/OpenRange.kt | 8 + .../main/java/com/icegps/math/range/Ranges.kt | 21 + math/src/main/java/com/icegps/memory/Bits.kt | 349 +++ .../main/java/com/icegps/memory/DoubleBits.kt | 47 + math/src/main/java/com/icegps/memory/Int64.kt | 194 ++ .../main/java/com/icegps/number/StringExt.kt | 71 + .../com/icegps/math/geometry/AngleTest.kt | 22 + .../icegps/math/geometry/EulerRotationTest.kt | 15 + .../java/com/icegps/number/NiceStrTest.kt | 15 + settings.gradle.kts | 37 + 168 files changed, 16142 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/generate-screenshots.yml create mode 100644 .github/workflows/pick-me.yml create mode 100644 .github/workflows/release-apidocs.yml create mode 100644 .github/workflows/release-candidate-to-maven-central.yml create mode 100644 .github/workflows/release-to-maven-central.yml create mode 100644 .gitignore create mode 100644 android/.gitignore create mode 100644 android/build.gradle.kts create mode 100644 android/proguard-rules.pro create mode 100644 android/src/androidTest/java/com/icegps/geotools/ExampleInstrumentedTest.kt create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/java/com/icegps/geotools/ContoursManager.kt create mode 100644 android/src/main/java/com/icegps/geotools/ControllableArrow.kt create mode 100644 android/src/main/java/com/icegps/geotools/CoordinateGenerator.kt create mode 100644 android/src/main/java/com/icegps/geotools/DisplaySlopeResult.kt create mode 100644 android/src/main/java/com/icegps/geotools/EarthworkManager.kt create mode 100644 android/src/main/java/com/icegps/geotools/GridDisplay.kt create mode 100644 android/src/main/java/com/icegps/geotools/GridModel.kt create mode 100644 android/src/main/java/com/icegps/geotools/MainActivity.kt create mode 100644 android/src/main/java/com/icegps/geotools/MainViewModel.kt create mode 100644 android/src/main/java/com/icegps/geotools/PolylineManager.kt create mode 100644 android/src/main/java/com/icegps/geotools/RayCastingAlgorithm.kt create mode 100644 android/src/main/java/com/icegps/geotools/SimplePalette.kt create mode 100644 android/src/main/java/com/icegps/geotools/catmullrom/CatmullRom.kt create mode 100644 android/src/main/java/com/icegps/geotools/color/ColorRGBa.kt create mode 100644 android/src/main/java/com/icegps/geotools/colorbrewer2/ColorBrewer2.kt create mode 100644 android/src/main/java/com/icegps/geotools/ktx/ColorRGBa.kt create mode 100644 android/src/main/java/com/icegps/geotools/ktx/Context.kt create mode 100644 android/src/main/java/com/icegps/geotools/ktx/Vector2D.kt create mode 100644 android/src/main/java/com/icegps/geotools/ktx/Vector3D.kt create mode 100644 android/src/main/java/com/icegps/geotools/marchingsquares/MarchingSquares.kt create mode 100644 android/src/main/res/drawable/ic_launcher_background.xml create mode 100644 android/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 android/src/main/res/layout-port/activity_main.xml create mode 100644 android/src/main/res/layout/activity_main.xml create mode 100644 android/src/main/res/mipmap-anydpi/ic_launcher.xml create mode 100644 android/src/main/res/mipmap-anydpi/ic_launcher_round.xml create mode 100644 android/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 android/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 android/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 android/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 android/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 android/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 android/src/main/res/values-night/themes.xml create mode 100644 android/src/main/res/values/colors.xml create mode 100644 android/src/main/res/values/mapbox_access_token.xml create mode 100644 android/src/main/res/values/strings.xml create mode 100644 android/src/main/res/values/themes.xml create mode 100644 android/src/test/java/com/icegps/geotools/ExampleUnitTest.kt create mode 100644 android/src/test/java/com/icegps/geotools/TriangulationToGridTest.kt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 icegps-common/.gitignore create mode 100644 icegps-common/build.gradle.kts create mode 100644 icegps-common/consumer-rules.pro create mode 100644 icegps-common/proguard-rules.pro create mode 100644 icegps-common/src/androidTest/java/com/icegps/common/ExampleInstrumentedTest.kt create mode 100644 icegps-common/src/main/AndroidManifest.xml create mode 100644 icegps-common/src/main/java/com/icegps/common/helper/BlhToEnu.kt create mode 100644 icegps-common/src/main/java/com/icegps/common/helper/GeoHelper.kt create mode 100644 icegps-common/src/test/java/com/icegps/common/ExampleUnitTest.kt create mode 100644 icegps-shared/.gitignore create mode 100644 icegps-shared/build.gradle.kts create mode 100644 icegps-shared/consumer-rules.pro create mode 100644 icegps-shared/proguard-rules.pro create mode 100644 icegps-shared/src/androidTest/java/com/icegps/shared/ExampleInstrumentedTest.kt create mode 100644 icegps-shared/src/main/AndroidManifest.xml create mode 100644 icegps-shared/src/main/java/com/icegps/shared/SharedHttpClient.kt create mode 100644 icegps-shared/src/main/java/com/icegps/shared/SharedJson.kt create mode 100644 icegps-shared/src/main/java/com/icegps/shared/api/LookupResponse.kt create mode 100644 icegps-shared/src/main/java/com/icegps/shared/api/OpenElevationApi.kt create mode 100644 icegps-shared/src/main/java/com/icegps/shared/ktx/Any.kt create mode 100644 icegps-shared/src/main/java/com/icegps/shared/model/GeoPoint.kt create mode 100644 icegps-shared/src/main/java/com/icegps/shared/model/IGeoPoint.kt create mode 100644 icegps-shared/src/test/java/com/icegps/shared/ExampleUnitTest.kt create mode 100644 icegps-triangulation/.gitignore create mode 100644 icegps-triangulation/build.gradle.kts create mode 100644 icegps-triangulation/src/main/java/com/icegps/triangulation/Delaunator.kt create mode 100644 icegps-triangulation/src/main/java/com/icegps/triangulation/Delaunay.kt create mode 100644 icegps-triangulation/src/main/java/com/icegps/triangulation/DelaunayTriangulation.kt create mode 100644 icegps-triangulation/src/main/java/com/icegps/triangulation/DoubleDouble.kt create mode 100644 icegps-triangulation/src/main/java/com/icegps/triangulation/Predicates.kt create mode 100644 icegps-triangulation/src/main/java/com/icegps/triangulation/Triangle.kt create mode 100644 math/.gitignore create mode 100644 math/build.gradle.kts create mode 100644 math/src/main/java/com/icegps/io/util/NumberExt.kt create mode 100644 math/src/main/java/com/icegps/io/util/NumberParser.kt create mode 100644 math/src/main/java/com/icegps/math/Alignment.kt create mode 100644 math/src/main/java/com/icegps/math/BooleanConversion.kt create mode 100644 math/src/main/java/com/icegps/math/Clamp.kt create mode 100644 math/src/main/java/com/icegps/math/ConvertRange.kt create mode 100644 math/src/main/java/com/icegps/math/Division.kt create mode 100644 math/src/main/java/com/icegps/math/Fract.kt create mode 100644 math/src/main/java/com/icegps/math/ILog.kt create mode 100644 math/src/main/java/com/icegps/math/IsAlmostEquals.kt create mode 100644 math/src/main/java/com/icegps/math/IsAlmostZero.kt create mode 100644 math/src/main/java/com/icegps/math/IsEven.kt create mode 100644 math/src/main/java/com/icegps/math/IsNanOrInfinite.kt create mode 100644 math/src/main/java/com/icegps/math/Math.kt create mode 100644 math/src/main/java/com/icegps/math/NormalizeZero.kt create mode 100644 math/src/main/java/com/icegps/math/PowerOfTwo.kt create mode 100644 math/src/main/java/com/icegps/math/RoundDecimalPlaces.kt create mode 100644 math/src/main/java/com/icegps/math/ToIntegerConverters.kt create mode 100644 math/src/main/java/com/icegps/math/Umod.kt create mode 100644 math/src/main/java/com/icegps/math/Unsigned.kt create mode 100644 math/src/main/java/com/icegps/math/annotations/_Math_annotations.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/AABB3D.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Anchor.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Angle.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/BoundsBuilder.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Circle.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Ellipse.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/EulerRotation.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/IPointList.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Line.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Line3D.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Margin.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Matrix.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Matrix3.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Matrix4.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Matrix4Ext.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/MatrixExt.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/MatrixMajorOrder.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Orientation.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Polygon.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Polyline.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Quaternion.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Ray.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/RectCorners.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Rectangle.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/RectangleInt.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/RoundRectangle.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Scale.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/ScaleMode.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Size.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Spacing.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/Sphere3D.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/VectorExt.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/VectorsDouble.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/VectorsFloat.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/VectorsInt.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/shape/SimpleShape2D.kt create mode 100644 math/src/main/java/com/icegps/math/geometry/shape/SimpleShape3D.kt create mode 100644 math/src/main/java/com/icegps/math/interpolation/Easing.kt create mode 100644 math/src/main/java/com/icegps/math/interpolation/Interpolation.vector.kt create mode 100644 math/src/main/java/com/icegps/math/interpolation/Ratio.kt create mode 100644 math/src/main/java/com/icegps/math/range/OpenRange.kt create mode 100644 math/src/main/java/com/icegps/math/range/Ranges.kt create mode 100644 math/src/main/java/com/icegps/memory/Bits.kt create mode 100644 math/src/main/java/com/icegps/memory/DoubleBits.kt create mode 100644 math/src/main/java/com/icegps/memory/Int64.kt create mode 100644 math/src/main/java/com/icegps/number/StringExt.kt create mode 100644 math/src/test/java/com/icegps/math/geometry/AngleTest.kt create mode 100644 math/src/test/java/com/icegps/math/geometry/EulerRotationTest.kt create mode 100644 math/src/test/java/com/icegps/number/NiceStrTest.kt create mode 100644 settings.gradle.kts diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..325cff7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,38 @@ +name: "Bug report" +description: Report an issue or possible bug +title: "Bug report: " +labels: ["bug"] +assignees: [] +body: + - type: markdown + attributes: + value: Thank you for taking the time to file a bug report! Please fill out this form as completely as possible. + - type: input + attributes: + label: Operating System + placeholder: Mac, Windows, Linux + validations: + required: true + - type: input + attributes: + label: OPENRNDR version + validations: + required: true + - type: input + attributes: + label: ORX version + validations: + required: true + - type: input + attributes: + label: Java version (if applicable) + - type: textarea + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is, optionally including a screenshot. + validations: + required: true + - type: textarea + attributes: + label: Steps to reproduce the bug + description: Describe the steps taken or paste/link a minimal code example that we can reproduce ourselves. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..2b26ea6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,30 @@ +name: "Feature request" +description: "Suggest an idea or general improvement." +title: "Feature request: " +labels: ["enhancement"] +assignees: [] +body: + - type: markdown + attributes: + value: Thanks for taking the time to suggest a new feature! Please fill out this form as completely as possible. + - type: textarea + attributes: + label: Motivation + description: | + Description of what the problem is and what the desired solution would be. + + **Include links to relevant issues or discussions, if there are any.** + placeholder: I want to be able to + validations: + required: true + - type: dropdown + attributes: + label: Help make it happen! + description: "Feature requests with contributing authors are much more likely to get done!" + multiple: false + options: + - I am not willing to submit a PR to implement this change. + - I am willing to submit a PR to implement this change, but would need some guidance. + - I am willing to submit a PR to implement this change. + validations: + required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1f3128e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,53 @@ +name: Build +on: + # Trigger the workflow on any pull request + pull_request: + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + working-directory: ./orx + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + with: + path: ./orx + + - name: Checkout OPENRNDR repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + repository: openrndr/openrndr + path: ./openrndr + ref: master + + - name: Test glxinfo + run: | + sudo apt-get update + sudo apt-get install -y mesa-utils xvfb + xvfb-run glxinfo + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build OPENRNDR + working-directory: ./openrndr + run: ./gradlew publishToMavenLocal snapshot + + - name: Build ORX + run: ./gradlew build + + - name: Collect screenshots without errors + run: xvfb-run ./gradlew collectScreenshots diff --git a/.github/workflows/generate-screenshots.yml b/.github/workflows/generate-screenshots.yml new file mode 100644 index 0000000..ac68029 --- /dev/null +++ b/.github/workflows/generate-screenshots.yml @@ -0,0 +1,69 @@ +name: Build and generate screenshots +on: + push: + branches: [ master ] + +defaults: + run: + working-directory: ./orx + +jobs: + generate_screenshots: + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + with: + path: ./orx + + - name: Checkout OPENRNDR repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + repository: openrndr/openrndr + path: ./openrndr + ref: master + + - name: Test glxinfo + run: | + sudo apt-get update + sudo apt-get install -y mesa-utils xvfb + xvfb-run glxinfo + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build OPENRNDR + working-directory: ./openrndr + run: ./gradlew publishToMavenLocal snapshot + + - name: Build ORX + run: ./gradlew build + + - name: Collect screenshots + run: xvfb-run ./gradlew collectScreenshots + + - name: Build main readme + run: xvfb-run ./gradlew buildMainReadme + + - name: Prepare media branch + run: | + git config --global user.email "actions@openrndr.org" + git config --global user.name "OPENRNDR Actions" + git reset HEAD -- . + (git add README.md && git commit -m "add auto-generated README" && git push origin master) || true + (git add [a-z-]*/README.md && git commit -m "add demos to README.md" && git push origin master) || true + (git add orx-jvm/[a-z-]*/README.md && git commit -m "add orx-jvm demos to README.md" && git push origin master) || true + git checkout --orphan media + git reset HEAD -- . + git add [a-z-]*/images/*.png + cd orx-jvm + git add [a-z-]*/images/*.png + cd .. + git commit -m "add auto-generated media" + git push -f origin media diff --git a/.github/workflows/pick-me.yml b/.github/workflows/pick-me.yml new file mode 100644 index 0000000..b7034b2 --- /dev/null +++ b/.github/workflows/pick-me.yml @@ -0,0 +1,13 @@ +name: Pick me issue assignment + +on: + issue_comment: + types: [created, edited] +jobs: + auto-assign: + runs-on: ubuntu-latest + steps: + - name: 'Auto-assign issue' + uses: edwinRNDR/pick-me@v0.3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-apidocs.yml b/.github/workflows/release-apidocs.yml new file mode 100644 index 0000000..4fee120 --- /dev/null +++ b/.github/workflows/release-apidocs.yml @@ -0,0 +1,65 @@ +name: Release API docs +on: + push: + branches: [ master ] + +defaults: + run: + working-directory: ./orx + +jobs: + release_apidocs: + if: github.repository == 'openrndr/orx' + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + with: + path: ./orx + fetch-depth: 0 + + - name: Checkout OPENRNDR repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + repository: openrndr/openrndr + path: ./openrndr + ref: master + + - name: Test glxinfo + run: | + sudo apt-get update + sudo apt-get install -y mesa-utils xvfb + xvfb-run glxinfo + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build OPENRNDR + working-directory: ./openrndr + run: ./gradlew publishToMavenLocal snapshot + + - name: Build apidocs + run: ./gradlew :dokkaGenerate -Dorg.gradle.jvmargs=-Xmx1536M + + - name: Publish to gh-pages + run: | + git worktree add --detach docs-temp + cd docs-temp + git checkout --orphan gh-pages + git reset HEAD -- . + git clean -df + mv ../build/dokka/html/* . + cp ../dokka/styles/* styles/ + echo orx.openrndr.org > CNAME + git status + git config --global user.email "actions@openrndr.org" + git config --global user.name "OPENRNDR Actions" + git add . + git commit -m "Add automatically generated API docs" + git push origin gh-pages --force diff --git a/.github/workflows/release-candidate-to-maven-central.yml b/.github/workflows/release-candidate-to-maven-central.yml new file mode 100644 index 0000000..0eb480b --- /dev/null +++ b/.github/workflows/release-candidate-to-maven-central.yml @@ -0,0 +1,25 @@ +name: Release candidate to Maven Central +on: + push: + tags: + - v[0-9].[0-9]+.[0-9]+-rc.[0-9]+ +jobs: + release_candidate_to_maven_central: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + - name: Build ORX + run: ./gradlew -Prelease.useLastTag=true build + - name: Decode + run: | + echo "${{secrets.SIGNING_SECRET_KEY_RING_FILE}}" > ~/.gradle/secring.gpg.b64 + base64 -d ~/.gradle/secring.gpg.b64 > ~/.gradle/secring.gpg + - name: Publish + run: ./gradlew publishAggregationToCentralPortal -Prelease.useLastTag=true -Psigning.keyId=${{secrets.SIGNING_KEY_ID}} -Psigning.password=openrndr -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg) + env: + OSSRH_USERNAME: ${{secrets.OSSRH_USERNAME}} + OSSRH_PASSWORD: ${{secrets.OSSRH_PASSWORD}} diff --git a/.github/workflows/release-to-maven-central.yml b/.github/workflows/release-to-maven-central.yml new file mode 100644 index 0000000..1e72424 --- /dev/null +++ b/.github/workflows/release-to-maven-central.yml @@ -0,0 +1,27 @@ +name: Release to Maven Central +on: + push: + tags: + - v[0-9].[0-9]+.[0-9]+ + - v[0-9].[0-9]+.[0-9]+-alpha[0-9]+ + - v[0-9].[0-9]+.[0-9]+-beta[0-9]+ +jobs: + release_to_maven_central: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + - name: Build ORX + run: ./gradlew -Prelease.useLastTag=true build + - name: Decode + run: | + echo "${{secrets.SIGNING_SECRET_KEY_RING_FILE}}" > ~/.gradle/secring.gpg.b64 + base64 -d ~/.gradle/secring.gpg.b64 > ~/.gradle/secring.gpg + - name: Publish + run: ./gradlew publishAggregationToCentralPortal -Prelease.useLastTag=true -Psigning.keyId=${{secrets.SIGNING_KEY_ID}} -Psigning.password=openrndr -Psigning.secretKeyRingFile=$(echo ~/.gradle/secring.gpg) + env: + OSSRH_USERNAME: ${{secrets.OSSRH_USERNAME}} + OSSRH_PASSWORD: ${{secrets.OSSRH_PASSWORD}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe3b002 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.kotlin \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/android/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..4111140 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,62 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.icegps.geotools" + compileSdk { + version = release(36) + } + + defaultConfig { + applicationId = "com.icegps.geotools" + minSdk = 28 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + buildFeatures { + viewBinding = true + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +kotlin { + compilerOptions.jvmTarget = JvmTarget.JVM_17 +} + +dependencies { + implementation(libs.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.activity) + implementation(libs.androidx.constraintlayout) + implementation(libs.mapbox.maps) + implementation(project(":math")) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(project(":icegps-common")) + implementation(project(":icegps-shared")) + implementation(project(":icegps-triangulation")) + + testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android/src/androidTest/java/com/icegps/geotools/ExampleInstrumentedTest.kt b/android/src/androidTest/java/com/icegps/geotools/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..b923350 --- /dev/null +++ b/android/src/androidTest/java/com/icegps/geotools/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.icegps.geotools + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.icegps.geotools", appContext.packageName) + } +} \ No newline at end of file diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d92b7ef --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/ContoursManager.kt b/android/src/main/java/com/icegps/geotools/ContoursManager.kt new file mode 100644 index 0000000..541c54b --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/ContoursManager.kt @@ -0,0 +1,427 @@ +package com.icegps.geotools + +import ColorBrewer2Type +import android.content.Context +import android.util.Log +import colorBrewer2Palettes +import com.icegps.math.geometry.Rectangle +import com.icegps.math.geometry.Vector2D +import com.icegps.math.geometry.Vector3D +import com.icegps.geotools.catmullrom.CatmullRomChain2 +import com.icegps.geotools.ktx.area +import com.icegps.geotools.ktx.toColorInt +import com.icegps.geotools.ktx.toMapboxPoint +import com.icegps.geotools.ktx.toast +import com.icegps.geotools.marchingsquares.ShapeContour +import com.icegps.geotools.marchingsquares.findContours +import com.icegps.shared.ktx.TAG +import com.icegps.triangulation.DelaunayTriangulation +import com.icegps.triangulation.Triangle +import com.mapbox.geojson.Feature +import com.mapbox.geojson.FeatureCollection +import com.mapbox.geojson.LineString +import com.mapbox.geojson.Polygon +import com.mapbox.maps.MapView +import com.mapbox.maps.Style +import com.mapbox.maps.extension.style.expressions.generated.Expression +import com.mapbox.maps.extension.style.layers.addLayer +import com.mapbox.maps.extension.style.layers.generated.fillLayer +import com.mapbox.maps.extension.style.layers.generated.lineLayer +import com.mapbox.maps.extension.style.layers.properties.generated.LineCap +import com.mapbox.maps.extension.style.layers.properties.generated.LineJoin +import com.mapbox.maps.extension.style.sources.addSource +import com.mapbox.maps.extension.style.sources.generated.geoJsonSource +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.math.max + +class ContoursManager( + private val context: Context, + private val mapView: MapView, + private val scope: CoroutineScope +) { + private val sourceId: String = "contours-source-id-10" + private val layerId: String = "contours-layer-id-10" + private val fillSourceId: String = "contours-fill-source-id-10" + private val fillLayerId: String = "contours-fill-layer-id-10" + private val gridSourceId: String = "grid-polygon-source-id" + private val gridLayerId: String = "grid-polygon-layer-id" + + private var contourSize: Int = 6 + private var heightRange: ClosedFloatingPointRange = 0.0..100.0 + private var cellSize: Double? = 10.0 + val simplePalette = SimplePalette( + range = 0.0..100.0 + ) + + private var colors = colorBrewer2Palettes( + numberOfColors = contourSize, + paletteType = ColorBrewer2Type.Any + ).first().colors.reversed() + + private var points: List = emptyList() + + private val polylineManager = PolylineManager(mapView) + + fun updateContourSize(contourSize: Int) { + this.contourSize = contourSize + colors = colorBrewer2Palettes( + numberOfColors = contourSize, + paletteType = ColorBrewer2Type.Any + ).first().colors.reversed() + } + + fun updateCellSize(value: Double) { + cellSize = value + } + + fun updatePoints( + points: List, + ) { + this.points = points + } + + fun updateHeightRange( + heightRange: ClosedFloatingPointRange? = null + ) { + if (heightRange == null) { + if (points.isEmpty()) { + return + } + val height = points.map { it.z } + val range = height.min()..height.max() + this.heightRange = range + simplePalette.setRange(range) + } else { + this.heightRange = heightRange + simplePalette.setRange(heightRange) + } + } + + private var isGridVisible: Boolean = true + private var _gridModel = MutableStateFlow(null) + val gridModel = _gridModel.asStateFlow() + + fun setGridVisible(visible: Boolean) { + if (visible != isGridVisible) { + isGridVisible = visible + if (visible) { + _gridModel.value?.let { gridModel -> + mapView.displayGridModel( + grid = gridModel, + sourceId = gridSourceId, + layerId = gridLayerId, + palette = simplePalette::palette + ) + } + } else { + mapView.mapboxMap.getStyle { style -> + try { + style.removeStyleLayer(gridLayerId) + } catch (_: Exception) { + } + + if (style.styleSourceExists(gridSourceId)) { + style.removeStyleSource(gridSourceId) + } + } + } + } + } + + private var triangles: List = listOf() + private var isTriangleVisible: Boolean = true + + fun setTriangleVisible(visible: Boolean) { + if (visible != isTriangleVisible) { + isTriangleVisible = visible + if (visible) { + polylineManager.update( + triangles.map { + listOf(it.x1, it.x2, it.x3) + .map { Vector3D(it.x, it.y, it.z) } + } + ) + } else { + polylineManager.clearContours() + } + } + } + + private var job: Job? = null + + fun refresh() { + val points = points + if (points.size <= 3) { + context.toast("points size ${points.size}") + return + } + job?.cancel() + scope.launch { + mapView.mapboxMap.getStyle { style -> + val step = heightRange.endInclusive / contourSize + val zip = (0..contourSize).map { index -> + heightRange.start + index * step + }.zipWithNext { a, b -> a..b } + val area = points.area + val triangulation = DelaunayTriangulation(points) + val triangles = triangulation.triangles() + val cellSize: Double = if (cellSize == null || cellSize!! < 0.1) { + (max(triangulation.points.area.width, triangulation.points.area.height) / 50) + } else { + cellSize!! + } + scope.launch { + val gridModel = triangulationToGrid( + delaunator = triangulation, + cellSize = cellSize, + ) + this@ContoursManager._gridModel.value = gridModel + if (isGridVisible) mapView.displayGridModel( + grid = gridModel, + sourceId = gridSourceId, + layerId = gridLayerId, + palette = simplePalette::palette + ) + } + job = scope.launch(Dispatchers.Default) { + val lineFeatures = mutableListOf>() + val features = zip.mapIndexed { index, range -> + async { + val contours = findContours( + triangles = triangles, + range = range, + area = area, + cellSize = cellSize + ) + val color = colors[index].toColorInt() + lineFeatures.add(contoursToLineFeatures(contours, color).flatten()) + contoursToPolygonFeatures(contours, color) + } + }.awaitAll() + withContext(Dispatchers.Main) { + if (false) setupLineLayer( + style = style, + sourceId = sourceId, + layerId = layerId, + features = lineFeatures.flatten() + ) + setupFillLayer( + style = style, + sourceId = fillSourceId, + layerId = fillLayerId, + features = features.filterNotNull(), + ) + Log.d(TAG, "refresh: 刷新完成") + } + } + } + } + } + + fun findContours( + triangles: List, + range: ClosedFloatingPointRange, + area: Rectangle, + cellSize: Double + ): List { + return findContours( + f = { v -> + val triangle = triangles.firstOrNull { triangle -> + isPointInTriangle3D(v, listOf(triangle.x1, triangle.x2, triangle.x3)) + } + (triangle?.let { triangle -> + val interpolate = interpolateHeight( + point = v, + triangle = listOf( + triangle.x1, + triangle.x2, + triangle.x3, + ) + ) + if (interpolate.z in range) -1.0 + else 1.0 + } ?: 1.0).also { + Log.d(TAG, "findContours: ${v} -> ${it}") + } + }, + area = area, + cellSize = cellSize, + ) + } + + private fun setupLineLayer( + style: Style, + sourceId: String, + layerId: String, + features: List + ) { + style.removeStyleLayer(layerId) + style.removeStyleSource(sourceId) + + val source = geoJsonSource(sourceId) { + featureCollection(FeatureCollection.fromFeatures(features)) + } + style.addSource(source) + + val layer = lineLayer(layerId, sourceId) { + lineColor(Expression.toColor(Expression.Companion.get("color"))) // 从属性获取颜色 + lineWidth(1.0) + lineCap(LineCap.ROUND) + lineJoin(LineJoin.ROUND) + lineOpacity(0.8) + } + style.addLayer(layer) + } + + private fun setupFillLayer( + style: Style, + sourceId: String, + layerId: String, + features: List + ) { + style.removeStyleLayer(layerId) + style.removeStyleSource(sourceId) + + val source = geoJsonSource(sourceId) { + featureCollection(FeatureCollection.fromFeatures(features)) + } + style.addSource(source) + + val layer = fillLayer(layerId, sourceId) { + fillColor(Expression.Companion.toColor(Expression.get("color"))) // 从属性获取颜色 + fillOpacity(0.5) + fillAntialias(true) + } + style.addLayer(layer) + } + + private var useCatmullRom: Boolean = true + + fun setCatmullRom(enabled: Boolean) { + useCatmullRom = enabled + } + + fun contoursToLineFeatures(contours: List, color: Int): List> { + return contours.drop(1).map { contour -> + contour.segments.map { segment -> + LineString.fromLngLats( + listOf( + segment.start.toMapboxPoint(), + segment.end.toMapboxPoint() + ) + ) + }.map { lineString -> + Feature.fromGeometry(lineString).apply { + // 将颜色Int转换为十六进制字符串 + addStringProperty("color", color.toHexColorString()) + } + } + } + } + + fun contoursToPolygonFeatures(contours: List, color: Int): Feature? { + val lists = contours.drop(0).filter { it.segments.isNotEmpty() }.map { contour -> + val start = contour.segments[0].start + listOf(start) + contour.segments.map { it.end } + }.map { + if (!useCatmullRom) return@map it + val cmr = CatmullRomChain2(it, 1.0, loop = true) + val contour = ShapeContour.fromPoints(cmr.positions(200), true) + val start = contour.segments[0].start + listOf(start) + contour.segments.map { it.end } + }.map { points -> points.map { it.toMapboxPoint() } } + + if (lists.isEmpty()) { + Log.w(TAG, "contoursToPolygonFeatures: 没有有效的轮廓数据") + return null + } + + val polygon = Polygon.fromLngLats(lists) + return Feature.fromGeometry(polygon).apply { + // 将颜色Int转换为十六进制字符串 + addStringProperty("color", color.toHexColorString()) + } + } + + fun Int.toHexColorString(): String { + return String.format("#%06X", 0xFFFFFF and this) + } + + fun clearContours() { + mapView.mapboxMap.getStyle { style -> + try { + style.removeStyleLayer(layerId) + } catch (_: Exception) { + } + try { + style.removeStyleSource(sourceId) + } catch (_: Exception) { + } + } + } +} + +fun isPointInTriangle3D(point: Vector2D, triangle: List): Boolean { + require(triangle.size == 3) { "三角形必须有3个顶点" } + + val (v1, v2, v3) = triangle + + // 计算重心坐标 + val denominator = (v2.y - v3.y) * (v1.x - v3.x) + (v3.x - v2.x) * (v1.y - v3.y) + if (denominator == 0.0) return false // 退化三角形 + + val alpha = ((v2.y - v3.y) * (point.x - v3.x) + (v3.x - v2.x) * (point.y - v3.y)) / denominator + val beta = ((v3.y - v1.y) * (point.x - v3.x) + (v1.x - v3.x) * (point.y - v3.y)) / denominator + val gamma = 1.0 - alpha - beta + + // 点在三角形内当且仅当所有重心坐标都在[0,1]范围内 + return alpha >= 0 && beta >= 0 && gamma >= 0 && + alpha <= 1 && beta <= 1 && gamma <= 1 +} + +/** + * 使用重心坐标计算点在三角形上的高度 + * + * @param point 二维点 (x, y) + * @param triangle 三角形的三个顶点 + * @return 三维点 (x, y, z) + */ +fun interpolateHeight(point: Vector2D, triangle: List): Vector3D { + /** + * 计算点在三角形中的重心坐标 + */ + fun calculateBarycentricCoordinates( + point: Vector2D, + v1: Vector3D, + v2: Vector3D, + v3: Vector3D + ): Triple { + val denom = (v2.y - v3.y) * (v1.x - v3.x) + (v3.x - v2.x) * (v1.y - v3.y) + + val alpha = ((v2.y - v3.y) * (point.x - v3.x) + (v3.x - v2.x) * (point.y - v3.y)) / denom + val beta = ((v3.y - v1.y) * (point.x - v3.x) + (v1.x - v3.x) * (point.y - v3.y)) / denom + val gamma = 1.0 - alpha - beta + + return Triple(alpha, beta, gamma) + } + + require(triangle.size == 3) { "三角形必须有3个顶点" } + + val (v1, v2, v3) = triangle + + // 计算重心坐标 + val (alpha, beta, gamma) = calculateBarycentricCoordinates(point, v1, v2, v3) + + // 使用重心坐标插值z值 + val z = alpha * v1.z + beta * v2.z + gamma * v3.z + + return Vector3D(point.x, point.y, z) +} diff --git a/android/src/main/java/com/icegps/geotools/ControllableArrow.kt b/android/src/main/java/com/icegps/geotools/ControllableArrow.kt new file mode 100644 index 0000000..dabb9ca --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/ControllableArrow.kt @@ -0,0 +1,197 @@ +package com.icegps.geotools + +import com.icegps.math.geometry.Angle +import com.icegps.math.geometry.Vector2D +import com.icegps.geotools.ktx.toMapboxPoint +import com.mapbox.geojson.Feature +import com.mapbox.geojson.FeatureCollection +import com.mapbox.geojson.LineString +import com.mapbox.geojson.Point +import com.mapbox.geojson.Polygon +import com.mapbox.maps.MapView +import com.mapbox.maps.Style +import com.mapbox.maps.extension.style.expressions.generated.Expression +import com.mapbox.maps.extension.style.layers.addLayer +import com.mapbox.maps.extension.style.layers.generated.FillLayer +import com.mapbox.maps.extension.style.layers.generated.LineLayer +import com.mapbox.maps.extension.style.layers.properties.generated.LineCap +import com.mapbox.maps.extension.style.layers.properties.generated.LineJoin +import com.mapbox.maps.extension.style.sources.addSource +import com.mapbox.maps.extension.style.sources.generated.geoJsonSource +import kotlin.math.cos +import kotlin.math.min +import kotlin.math.sin + +/** + * 设置趋势箭头图层 + */ +fun setupTrendLayer( + style: Style, + trendSourceId: String, + trendLayerId: String, + features: List +) { + val trendSource = geoJsonSource(trendSourceId) { + featureCollection(FeatureCollection.fromFeatures(features)) + } + + try { + style.removeStyleLayer(trendLayerId) + } catch (_: Exception) { + } + + try { + style.removeStyleLayer("$trendLayerId-head") + } catch (_: Exception) { + } + + if (style.styleSourceExists(trendSourceId)) { + style.removeStyleSource(trendSourceId) + } + + style.addSource(trendSource) + + val lineLayer = LineLayer(trendLayerId, trendSourceId).apply { + lineColor(Expression.toColor(Expression.get("color"))) + lineWidth(4.0) + lineCap(LineCap.ROUND) + lineJoin(LineJoin.ROUND) + } + style.addLayer(lineLayer) + + val headLayer = FillLayer("$trendLayerId-head", trendSourceId).apply { + fillColor(Expression.toColor(Expression.get("color"))) + } + style.addLayer(headLayer) +} + +fun MapView.displayControllableArrow( + grid: GridModel, + sourceId: String = "controllable-source-id-0", + layerId: String = "controllable-layer-id-0", + arrowScale: Double = 0.4, + angle: Angle, + onHeadArrowChange: (List) -> Unit +) { + mapboxMap.getStyle { style -> + val centerX = (grid.minX + grid.maxX) / 2 + val centerY = (grid.minY + grid.maxY) / 2 + + val regionWidth = grid.maxX - grid.minX + val regionHeight = grid.maxY - grid.minY + val arrowLength = min(regionWidth, regionHeight) * arrowScale * 1.0 + + val arrowDirectionRad = angle.radians + val endX = centerX + sin(arrowDirectionRad) * arrowLength + val endY = centerY + cos(arrowDirectionRad) * arrowLength + + val arrowLine = LineString.fromLngLats( + listOf( + Vector2D(centerX, centerY), + Vector2D(endX, endY) + ).map { it.toMapboxPoint() } + ) + + val arrowFeature = Feature.fromGeometry(arrowLine) + arrowFeature.addStringProperty("color", "#0000FF") + arrowFeature.addStringProperty("type", "overall-trend") + + // 创建箭头头部 + val headSize = arrowLength * 0.2 + val leftRad = arrowDirectionRad + Math.PI * 0.8 + val rightRad = arrowDirectionRad - Math.PI * 0.8 + + val leftX = endX + sin(leftRad) * headSize + val leftY = endY + cos(leftRad) * headSize + val rightX = endX + sin(rightRad) * headSize + val rightY = endY + cos(rightRad) * headSize + + val headRing = listOf( + Vector2D(endX, endY), + Vector2D(leftX, leftY), + Vector2D(rightX, rightY), + Vector2D(endX, endY) + ).map { it.toMapboxPoint() } + onHeadArrowChange(headRing) + val headPolygon = Polygon.fromLngLats(listOf(headRing)) + val headFeature = Feature.fromGeometry(headPolygon) + headFeature.addStringProperty("color", "#0000FF") + headFeature.addStringProperty("type", "overall-trend") + + val features = listOf(arrowFeature, headFeature) + + // 设置图层 + setupTrendLayer(style, sourceId, layerId, features) + } +} + +fun calculateArrowData( + grid: GridModel, + angle: Angle, + arrowScale: Double = 0.4 +): ArrowData { + val centerX = (grid.minX + grid.maxX) / 2 + val centerY = (grid.minY + grid.maxY) / 2 + + val regionWidth = grid.maxX - grid.minX + val regionHeight = grid.maxY - grid.minY + val arrowLength = min(regionWidth, regionHeight) * arrowScale * 1.0 + + val arrowDirectionRad = angle.radians + val endX = centerX + sin(arrowDirectionRad) * arrowLength + val endY = centerY + cos(arrowDirectionRad) * arrowLength + + val arrowLine = listOf( + Vector2D(centerX, centerY), + Vector2D(endX, endY) + ) + + // 创建箭头头部 + val headSize = arrowLength * 0.2 + val leftRad = arrowDirectionRad + Math.PI * 0.8 + val rightRad = arrowDirectionRad - Math.PI * 0.8 + + val leftX = endX + sin(leftRad) * headSize + val leftY = endY + cos(leftRad) * headSize + val rightX = endX + sin(rightRad) * headSize + val rightY = endY + cos(rightRad) * headSize + + val headRing = listOf( + Vector2D(endX, endY), + Vector2D(leftX, leftY), + Vector2D(rightX, rightY), + Vector2D(endX, endY) + ) + return ArrowData( + arrowLine = arrowLine, + headRing = headRing + ) +} + +data class ArrowData( + val arrowLine: List, + val headRing: List +) + +fun MapView.displayControllableArrow( + sourceId: String = "controllable-source-id-0", + layerId: String = "controllable-layer-id-0", + arrowData: ArrowData +) { + mapboxMap.getStyle { style -> + val (arrowLine, headRing) = arrowData + val arrowFeature = Feature.fromGeometry(LineString.fromLngLats(arrowLine.map { it.toMapboxPoint() })) + arrowFeature.addStringProperty("color", "#0000FF") + arrowFeature.addStringProperty("type", "overall-trend") + + val headPolygon = Polygon.fromLngLats(listOf(headRing.map { it.toMapboxPoint() })) + val headFeature = Feature.fromGeometry(headPolygon) + headFeature.addStringProperty("color", "#0000FF") + headFeature.addStringProperty("type", "overall-trend") + + val features = listOf(arrowFeature, headFeature) + + // 设置图层 + setupTrendLayer(style, sourceId, layerId, features) + } +} \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/CoordinateGenerator.kt b/android/src/main/java/com/icegps/geotools/CoordinateGenerator.kt new file mode 100644 index 0000000..8fc533a --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/CoordinateGenerator.kt @@ -0,0 +1,53 @@ +package com.icegps.geotools + +import com.icegps.math.geometry.Angle +import com.icegps.math.geometry.Vector3D +import com.icegps.math.geometry.degrees +import kotlin.math.cos +import kotlin.math.sin +import kotlin.random.Random + +/** + * @author tabidachinokaze + * @date 2025/11/25 + */ +fun coordinateGenerate(): List { + val minX = -20.0 + val maxX = 20.0 + val minY = -20.0 + val maxY = 20.0 + val minZ = -20.0 + val maxZ = 20.0 + val x: () -> Double = { Random.nextDouble(minX, maxX) } + val y: () -> Double = { Random.nextDouble(minY, maxY) } + val z: () -> Double = { Random.nextDouble(minZ, maxZ) } + val dPoints = (0..60).map { + Vector3D(x(), y(), z()) + } + return dPoints +} + +fun coordinateGenerate1(): List> { + /** + * 绕 Z 轴旋转指定角度(弧度) + */ + fun Vector3D.rotateAroundZ(angle: Angle): Vector3D { + val cosAngle = cos(angle.radians) + val sinAngle = sin(angle.radians) + + return Vector3D( + x = x * cosAngle - y * sinAngle, + y = x * sinAngle + y * cosAngle, + z = z + ) + } + + val center = Vector3D() + val direction = Vector3D(0.0, 1.0, -1.0) + return (0..360).step(10).map { + val nowDirection = direction.rotateAroundZ(it.degrees) + listOf(2, 6, 10).map { + center + nowDirection * it + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/DisplaySlopeResult.kt b/android/src/main/java/com/icegps/geotools/DisplaySlopeResult.kt new file mode 100644 index 0000000..3237be5 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/DisplaySlopeResult.kt @@ -0,0 +1,144 @@ +package com.icegps.geotools + +import android.util.Log +import com.icegps.math.geometry.Vector2D +import com.icegps.geotools.ktx.toMapboxPoint +import com.mapbox.geojson.Feature +import com.mapbox.geojson.FeatureCollection +import com.mapbox.geojson.Polygon +import com.mapbox.maps.MapView +import com.mapbox.maps.Style +import com.mapbox.maps.extension.style.expressions.generated.Expression +import com.mapbox.maps.extension.style.layers.addLayer +import com.mapbox.maps.extension.style.layers.generated.FillLayer +import com.mapbox.maps.extension.style.layers.generated.LineLayer +import com.mapbox.maps.extension.style.sources.addSource +import com.mapbox.maps.extension.style.sources.generated.geoJsonSource + +/** + * @author tabidachinokaze + * @date 2025/11/26 + */ +/** + * 绘制斜坡设计结果 + */ +fun MapView.displaySlopeResult( + originalGrid: GridModel, + slopeResult: SlopeResult, + sourceId: String = "slope-result", + layerId: String = "slope-layer", + palette: (Double?) -> String, + showDesignHeight: Boolean +) { + val elevationList = mutableListOf() + mapboxMap.getStyle { style -> + val features = mutableListOf() + val designGrid = slopeResult.designSurface + + // 对比测试,将绘制到原来图形的左边 + // val minX = originalGrid.minX * 2 - originalGrid.maxX + val minX = originalGrid.minX + val maxY = originalGrid.maxY + + val cellSize = originalGrid.cellSize + + for (r in 0 until originalGrid.rows) { + for (c in 0 until originalGrid.cols) { + val originalElev = originalGrid.getValue(r, c) ?: continue + val designElev = designGrid.getValue(r, c) ?: continue + elevationList.add(designElev) + + // 计算填挖高度 + val heightDiff = designElev - originalElev + + // 计算栅格边界 + val x0 = minX + c * cellSize + val y0 = maxY - r * cellSize + val x1 = x0 + cellSize + val y1 = y0 - cellSize + + // 1. 创建多边形要素(背景色) + val ring = listOf( + Vector2D(x0, y0), + Vector2D(x1, y0), + Vector2D(x1, y1), + Vector2D(x0, y1), + Vector2D(x0, y0) + ).map { it.toMapboxPoint() } + val poly = Polygon.fromLngLats(listOf(ring)) + val feature = Feature.fromGeometry(poly) + + if (showDesignHeight) { + // 显示设计高度,测试坡向是否正确,和高度是否计算正确 + feature.addStringProperty("color", palette(designElev)) + } else { + // 显示高差 + feature.addStringProperty("color", palette(heightDiff)) + } + // 显示原始高度 + // feature.addStringProperty("color", palette(originalElev)) + features.add(feature) + } + } + + Log.d("displayGridWithDirectionArrows", "对比区域的土方量计算: ${elevationList.sum()}, 平均值:${elevationList.average()}") + + // 设置图层 + setupEarthworkLayer(style, sourceId, layerId, features) + } +} + +/** + * 完整的土方工程图层设置 - 修正版 + */ +private fun setupEarthworkLayer( + style: Style, + sourceId: String, + layerId: String, + features: List, +) { + // 创建数据源 + val source = geoJsonSource(sourceId) { + featureCollection(FeatureCollection.fromFeatures(features)) + } + + // 清理旧图层 + try { + style.removeStyleLayer(layerId) + } catch (_: Exception) { + } + try { + style.removeStyleLayer("$layerId-arrow") + } catch (_: Exception) { + } + try { + style.removeStyleLayer("$layerId-outline") + } catch (_: Exception) { + } + try { + style.removeStyleLayer("$layerId-text") + } catch (_: Exception) { + } + + if (style.styleSourceExists(sourceId)) { + style.removeStyleSource(sourceId) + } + + // 添加数据源 + style.addSource(source) + + // 主填充图层 + val fillLayer = FillLayer(layerId, sourceId).apply { + fillColor(Expression.toColor(Expression.get("color"))) + fillOpacity(0.7) + } + style.addLayer(fillLayer) + + // 边框图层 + val outlineLayer = LineLayer("$layerId-outline", sourceId).apply { + lineColor("#333333") + lineWidth(1.0) + lineOpacity(0.5) + } + style.addLayer(outlineLayer) +} \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/EarthworkManager.kt b/android/src/main/java/com/icegps/geotools/EarthworkManager.kt new file mode 100644 index 0000000..0ed2eb6 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/EarthworkManager.kt @@ -0,0 +1,438 @@ +package com.icegps.geotools + +import android.graphics.PointF +import android.util.Log +import com.icegps.common.helper.GeoHelper +import com.icegps.math.geometry.Angle +import com.icegps.math.geometry.Vector2D +import com.icegps.math.geometry.degrees +import com.icegps.shared.ktx.TAG +import com.mapbox.android.gestures.MoveGestureDetector +import com.mapbox.geojson.Point +import com.mapbox.maps.MapView +import com.mapbox.maps.ScreenCoordinate +import com.mapbox.maps.plugin.gestures.OnMoveListener +import com.mapbox.maps.plugin.gestures.addOnMoveListener +import com.mapbox.maps.plugin.gestures.removeOnMoveListener +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.launchIn +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.sin + +/** + * @author tabidachinokaze + * @date 2025/11/26 + */ +object SlopeCalculator { + fun calculateSlope( + grid: GridModel, + slopeDirection: Double, + slopePercentage: Double, + baseHeightOffset: Double = 0.0 + ): SlopeResult { + val centerX = (grid.minX + grid.maxX) / 2 + val centerY = (grid.minY + grid.maxY) / 2 + + val elevations = grid.cells.filterNotNull() + val baseElevation = elevations.average() + baseHeightOffset + + val basePoint = Triple(centerX, centerY, baseElevation) + + val earthworkResult = EarthworkCalculator.calculateForSlopeDesign( + grid = grid, + basePoint = basePoint, + slope = slopePercentage, + aspect = slopeDirection + ) + + return SlopeResult( + slopeDirection = slopeDirection, + slopePercentage = slopePercentage, + baseHeightOffset = baseHeightOffset, + baseElevation = baseElevation, + earthworkResult = earthworkResult, + designSurface = generateSlopeDesignGrid( + grid = grid, + basePoint = basePoint, + slopePercentage = slopePercentage, + slopeDirection = slopeDirection + ) + ) + } + + + /** + * 生成斜坡设计面网格(用于可视化) + */ + private fun generateSlopeDesignGrid( + grid: GridModel, + basePoint: Triple, + slopePercentage: Double, + slopeDirection: Double + ): GridModel { + val designCells = Array(grid.rows * grid.cols) { null } + val (baseX, baseY, baseElev) = basePoint + val slopeRatio = slopePercentage / 100.0 + + for (r in 0 until grid.rows) { + for (c in 0 until grid.cols) { + if (grid.getValue(r, c) != null) { + val cellX = grid.minX + (c + 0.5) * (grid.maxX - grid.minX) / grid.cols + val cellY = grid.minY + (r + 0.5) * (grid.maxY - grid.minY) / grid.rows + + val designElev = calculateSlopeElevation( + pointX = cellX, + pointY = cellY, + baseX = baseX, + baseY = baseY, + baseElev = baseElev, + slopeRatio = slopeRatio, + slopeDirection = slopeDirection + ) + designCells[r * grid.cols + c] = designElev + } + } + } + return GridModel( + minX = grid.minX, + maxX = grid.maxX, + minY = grid.minY, + maxY = grid.maxY, + rows = grid.rows, + cols = grid.cols, + cellSize = grid.cellSize, + cells = designCells + ) + } + + /** + * 斜坡高程计算 + */ + fun calculateSlopeElevation( + pointX: Double, + pointY: Double, + baseX: Double, + baseY: Double, + baseElev: Double, + slopeRatio: Double, + slopeDirection: Double + ): Double { + val dx = (pointX - baseX) * cos(Math.toRadians(baseY)) + val dy = (pointY - baseY) + + val slopeRad = (slopeDirection.degrees - 90.degrees).normalized.radians + + val projection = dx * cos(slopeRad) + dy * sin(slopeRad) + val heightDiff = projection * slopeRatio + + return baseElev + heightDiff + } +} + +/** + * 斜面设计 + * + * @property slopeDirection 坡向 (度) + * @property slopePercentage 坡度 (%) + * @property baseHeightOffset 基准面高度偏移 (m) + * @property baseElevation 基准点高程 (m) + * @property earthworkResult 土方量结果 + * @property designSurface 设计面网格(用于可视化) + */ +data class SlopeResult( + val slopeDirection: Double, + val slopePercentage: Double, + val baseHeightOffset: Double, + val baseElevation: Double, + val earthworkResult: EarthworkResult, + val designSurface: GridModel +) + +object EarthworkCalculator { + /** + * @param grid 栅格网模型 + * @param designElevation 设计高程 + */ + fun calculateForFlatDesign( + grid: GridModel, + designElevation: Double + ): EarthworkResult { + var cutVolume = 0.0 + var fillVolume = 0.0 + var cutArea = 0.0 + var fillArea = 0.0 + val cellArea = grid.cellSize * grid.cellSize + + for (r in 0 until grid.rows) { + for (c in 0 until grid.cols) { + val originalElev = grid.getValue(r, c) ?: continue + + val heightDiff = designElevation - originalElev + + val volume = heightDiff * cellArea + + if (volume > 0) { + fillVolume += volume + fillArea += cellArea + } else if (volume < 0) { + cutVolume += abs(volume) + cutArea += cellArea + } + } + } + + return EarthworkResult( + cutVolume = cutVolume, + fillVolume = fillVolume, + netVolume = fillVolume - cutVolume, + cutArea = cutArea, + fillArea = fillArea, + totalArea = cutArea + fillArea + ) + } + + /** + * 计算斜面设计的土方量 + */ + fun calculateForSlopeDesign( + grid: GridModel, + basePoint: Triple, + slope: Double, + aspect: Double + ): EarthworkResult { + var cutVolume = 0.0 + var fillVolume = 0.0 + var cutArea = 0.0 + var fillArea = 0.0 + val cellArea = grid.cellSize * grid.cellSize + + val (baseX, baseY, baseElev) = basePoint + val slopeRatio = slope / 100.0 + + for (r in 0 until grid.rows) { + for (c in 0 until grid.cols) { + val originalElev = grid.getValue(r, c) ?: continue + + val cellX = grid.minX + (c + 0.5) * (grid.maxX - grid.minX) / grid.cols + val cellY = grid.minY + (r + 0.5) * (grid.maxY - grid.minY) / grid.rows + + val designElev = SlopeCalculator.calculateSlopeElevation( + pointX = cellX, + pointY = cellY, + baseX = baseX, + baseY = baseY, + baseElev = baseElev, + slopeRatio = slopeRatio, + slopeDirection = aspect + ) + + val heightElev = designElev - originalElev + val volume = heightElev * cellArea + + if (volume > 0) { + fillVolume += volume + fillArea += cellArea + } else if (volume < 0) { + cutVolume += abs(volume) + cutArea += cellArea + } + } + } + + return EarthworkResult( + cutVolume = cutVolume, + fillVolume = fillVolume, + netVolume = fillVolume - cutVolume, + cutArea = cutArea, + fillArea = fillArea, + totalArea = cutArea + fillArea + ) + } +} + +/** + * 土方量计算结果 + * @property cutVolume 挖方量 (m³) + * @property fillVolume 填方量 (m³) + * @property netVolume 净土方量 (m³) + * @property cutArea 挖方面积 (m²) + * @property fillArea 填方面积 (m²) + * @property totalArea 总面积 (m²) + */ +data class EarthworkResult( + val cutVolume: Double, + val fillVolume: Double, + val netVolume: Double, + val cutArea: Double, + val fillArea: Double, + val totalArea: Double +) { + override fun toString(): String { + return buildString { + appendLine("EarthworkResult") + appendLine("挖方: ${"%.1f".format(cutVolume)} m³") + appendLine("填方: ${"%.1f".format(fillVolume)} m³") + appendLine("净土方: ${"%.1f".format(netVolume)} m³") + appendLine("挖方面积: ${"%.1f".format(cutArea)} m²") + appendLine("填方面积: ${"%.1f".format(fillArea)} m²") + appendLine("总面积:${"%.1f".format(totalArea)} m²") + } + } +} + +class EarthworkManager( + private val mapView: MapView, + private val scope: CoroutineScope +) { + private val arrowSourceId: String = "controllable-source-id-0" + private val arrowLayerId: String = "controllable-layer-id-0" + private var listener: OnMoveListener? = null + + private var gridModel = MutableStateFlow(null) + private val arrowHead = MutableStateFlow(emptyList()) + private var arrowCenter = MutableStateFlow(Vector2D(0.0, 0.0)) + private var arrowEnd = MutableStateFlow(Vector2D(0.0, 1.0)) + private var _slopeDirection = MutableStateFlow(0.degrees) + val slopeDirection = _slopeDirection.asStateFlow() + private val _slopePercentage = MutableStateFlow(90.0) + val slopePercentage = _slopePercentage.asStateFlow() + private val _baseHeightOffset = MutableStateFlow(0.0) + val baseHeightOffset = _baseHeightOffset.asStateFlow() + + init { + combine( + arrowCenter, + arrowEnd, + gridModel + ) { center, arrow, gridModel -> + gridModel?.let { gridModel -> + // _slopeDirection.value = angle + displayControllableArrow(gridModel, getSlopeDirection(arrow, center)) + } + }.launchIn(scope) + combine( + _slopeDirection, + gridModel + ) { slopeDirection, gridModel -> + gridModel?.let { + displayControllableArrow(it, slopeDirection) + } + }.launchIn(scope) + } + + private fun getSlopeDirection( + arrow: Vector2D, + center: Vector2D + ): Angle { + val direction = (arrow - center) + val atan2 = Angle.atan2(direction.x, direction.y, Vector2D.UP) + val angle = atan2.normalized + return angle + } + + private fun displayControllableArrow(gridModel: GridModel, slopeDirection: Angle) { + val arrowData = calculateArrowData( + grid = gridModel, + angle = slopeDirection, + ) + arrowHead.value = arrowData.headRing + mapView.displayControllableArrow( + sourceId = arrowSourceId, + layerId = arrowLayerId, + arrowData = arrowData, + ) + } + + fun Point.toVector2D(): Vector2D { + val geoHelper = GeoHelper.getSharedInstance() + val enu = geoHelper.wgs84ToENU(lon = longitude(), lat = latitude(), hgt = 0.0) + return Vector2D(enu.x, enu.y) + } + + fun removeOnMoveListener() { + listener?.let(mapView.mapboxMap::removeOnMoveListener) + listener = null + } + + fun setupOnMoveListener() { + listener = object : OnMoveListener { + private var beginning: Boolean = false + private var isDragging: Boolean = false + private fun getCoordinate(focalPoint: PointF): Point { + return mapView.mapboxMap.coordinateForPixel(ScreenCoordinate(focalPoint.x.toDouble(), focalPoint.y.toDouble())) + } + + override fun onMove(detector: MoveGestureDetector): Boolean { + val focalPoint = detector.focalPoint + val point = mapView.mapboxMap + .coordinateForPixel(ScreenCoordinate(focalPoint.x.toDouble(), focalPoint.y.toDouble())) + .toVector2D() + + val isPointInPolygon = RayCastingAlgorithm.isPointInPolygon( + point = point, + polygon = arrowHead.value + ) + + if (isPointInPolygon) { + isDragging = true + } + if (isDragging) { + arrowEnd.value = point + } + return isDragging + } + + override fun onMoveBegin(detector: MoveGestureDetector) { + Log.d(TAG, "onMoveBegin: $detector") + beginning = true + } + + override fun onMoveEnd(detector: MoveGestureDetector) { + Log.d(TAG, "onMoveEnd: $detector") + val point = getCoordinate(detector.focalPoint) + val arrow = point.toVector2D() + if (beginning && isDragging) { + arrowEnd.value = arrow + val center = arrowCenter.value + _slopeDirection.value = getSlopeDirection(arrow, center) + } + Log.d( + TAG, + buildString { + appendLine("onMoveEnd: ") + appendLine("${point.longitude()}, ${point.latitude()}") + } + ) + isDragging = false + beginning = false + } + }.also(mapView.mapboxMap::addOnMoveListener) + } + + fun updateGridModel(gridModel: GridModel) { + this.gridModel.value = gridModel + calculateArrowCenter(gridModel) + } + + private fun calculateArrowCenter(gridModel: GridModel) { + val centerX = (gridModel.minX + gridModel.maxX) / 2 + val centerY = (gridModel.minY + gridModel.maxY) / 2 + arrowCenter.value = Vector2D(centerX, centerY) + } + + fun updateSlopeDirection(angle: Angle) { + _slopeDirection.value = angle + } + + fun updateSlopePercentage(value: Double) { + _slopePercentage.value = value + } + + fun updateDesignHeight(value: Double) { + _baseHeightOffset.value = value + } +} \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/GridDisplay.kt b/android/src/main/java/com/icegps/geotools/GridDisplay.kt new file mode 100644 index 0000000..43b120a --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/GridDisplay.kt @@ -0,0 +1,83 @@ +package com.icegps.geotools + +import com.icegps.common.helper.GeoHelper +import com.icegps.math.geometry.Vector2D +import com.mapbox.geojson.Feature +import com.mapbox.geojson.FeatureCollection +import com.mapbox.geojson.Point +import com.mapbox.geojson.Polygon +import com.mapbox.maps.MapView +import com.mapbox.maps.extension.style.expressions.generated.Expression +import com.mapbox.maps.extension.style.layers.addLayer +import com.mapbox.maps.extension.style.layers.generated.FillLayer +import com.mapbox.maps.extension.style.sources.addSource +import com.mapbox.maps.extension.style.sources.generated.geoJsonSource + +/** + * @author tabidachinokaze + * @date 2025/11/25 + */ +fun MapView.displayGridModel( + grid: GridModel, + sourceId: String, + layerId: String, + palette: (Double?) -> String, +) { + val geoHelper = GeoHelper.getSharedInstance() + mapboxMap.getStyle { style -> + val polygonFeatures = mutableListOf() + + val minX = grid.minX + val maxY = grid.maxY + val cellSize = grid.cellSize + + for (r in 0 until grid.rows) { + for (c in 0 until grid.cols) { + val idx = r * grid.cols + c + val v = grid.cells[idx] ?: continue + + val x0 = minX + c * cellSize + val y0 = maxY - r * cellSize + val x1 = x0 + cellSize + val y1 = y0 - cellSize + + val ring = listOf( + Vector2D(x0, y0), + Vector2D(x1, y0), + Vector2D(x1, y1), + Vector2D(x0, y1), + Vector2D(x0, y0), + ).map { + geoHelper.enuToWGS84Object(GeoHelper.ENU(it.x, it.y)) + }.map { + Point.fromLngLat(it.lon, it.lat) + } + val poly = Polygon.fromLngLats(listOf(ring)) + val polyFeature = Feature.fromGeometry(poly) + polyFeature.addStringProperty("color", palette(v)) + polyFeature.addNumberProperty("value", v ?: -9999.0) + polygonFeatures.add(polyFeature) + } + } + + try { + style.removeStyleLayer(layerId) + } catch (_: Exception) { + } + + if (style.styleSourceExists(sourceId)) { + style.removeStyleSource(sourceId) + } + + val polygonSource = geoJsonSource(sourceId) { + featureCollection(FeatureCollection.fromFeatures(polygonFeatures)) + } + style.addSource(polygonSource) + + val fillLayer = FillLayer(layerId, sourceId).apply { + fillColor(Expression.toColor(Expression.get("color"))) + fillOpacity(0.5) + } + style.addLayer(fillLayer) + } +} diff --git a/android/src/main/java/com/icegps/geotools/GridModel.kt b/android/src/main/java/com/icegps/geotools/GridModel.kt new file mode 100644 index 0000000..300ad35 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/GridModel.kt @@ -0,0 +1,139 @@ +package com.icegps.geotools + +import com.icegps.math.geometry.Vector2D +import com.icegps.math.geometry.Vector3D +import com.icegps.triangulation.DelaunayTriangulation +import kotlin.math.absoluteValue +import kotlin.math.ceil + +/** + * @author tabidachinokaze + * @date 2025/11/25 + */ +data class GridModel( + val minX: Double, + val maxX: Double, + val minY: Double, + val maxY: Double, + val rows: Int, + val cols: Int, + val cellSize: Double, + val cells: Array +) { + fun getValue(row: Int, col: Int): Double? { + if (row !in 0..= cols) { + return null + } + return cells[row * cols + col] + } +} + +fun triangulationToGrid( + delaunator: DelaunayTriangulation, + cellSize: Double = 50.0, + maxSidePixels: Int = 5000 +): GridModel { + fun pointInTriangle(pt: Vector2D, a: Vector3D, b: Vector3D, c: Vector3D): Boolean { + val v0x = c.x - a.x + val v0y = c.y - a.y + val v1x = b.x - a.x + val v1y = b.y - a.y + val v2x = pt.x - a.x + val v2y = pt.y - a.y + + val dot00 = v0x * v0x + v0y * v0y + val dot01 = v0x * v1x + v0y * v1y + val dot02 = v0x * v2x + v0y * v2y + val dot11 = v1x * v1x + v1y * v1y + val dot12 = v1x * v2x + v1y * v2y + + val denom = dot00 * dot11 - dot01 * dot01 + if (denom == 0.0) return false + val invDenom = 1.0 / denom + val u = (dot11 * dot02 - dot01 * dot12) * invDenom + val v = (dot00 * dot12 - dot01 * dot02) * invDenom + return u >= 0 && v >= 0 && u + v <= 1 + } + + fun barycentricInterpolateLegacy(pt: Vector2D, a: Vector3D, b: Vector3D, c: Vector3D, values: DoubleArray): Double { + val area = { p1: Vector2D, p2: Vector3D, p3: Vector3D -> + ((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)).absoluteValue / 2.0 + } + val area2 = { p1: Vector3D, p2: Vector3D, p3: Vector3D -> + ((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)).absoluteValue / 2.0 + } + val areaTotal = area2(a, b, c) + if (areaTotal == 0.0) return values[0] + val wA = area(pt, b, c) / areaTotal + val wB = area(pt, c, a) / areaTotal + val wC = area(pt, a, b) / areaTotal + return values[0] * wA + values[1] * wB + values[2] * wC + } + + + val pts = delaunator.points + require(pts.isNotEmpty()) { "points empty" } + + val x = pts.map { it.x } + val y = pts.map { it.y } + val minX = x.min() + val maxX = x.max() + val minY = y.min() + val maxY = y.max() + + val width = maxX - minX + val height = maxY - minY + + var cols = ceil(width / cellSize).toInt() + var rows = ceil(height / cellSize).toInt() + + // 防止过大 + if (cols > maxSidePixels) cols = maxSidePixels + if (rows > maxSidePixels) rows = maxSidePixels + + val cells = Array(rows * cols) { null } + + + val triangles = delaunator.triangles() + + for (ti in 0 until triangles.size) { + val (a, b, c) = triangles[ti] + + val tminX = minOf(a.x, b.x, c.x) + val tmaxX = maxOf(a.x, b.x, c.x) + val tminY = minOf(a.y, b.y, c.y) + val tmaxY = maxOf(a.y, b.y, c.y) + + val colMin = ((tminX - minX) / cellSize).toInt().coerceIn(0, cols - 1) + val colMax = ((tmaxX - minX) / cellSize).toInt().coerceIn(0, cols - 1) + val rowMin = ((maxY - tmaxY) / cellSize).toInt().coerceIn(0, rows - 1) + val rowMax = ((maxY - tminY) / cellSize).toInt().coerceIn(0, rows - 1) + + val triVertexVals = doubleArrayOf(a.z, b.z, c.z) + + for (r in rowMin..rowMax) { + for (cIdx in colMin..colMax) { + val centerX = minX + (cIdx + 0.5) * cellSize + val centerY = maxY - (r + 0.5) * cellSize + val pt = Vector2D(centerX, centerY) + if (pointInTriangle(pt, a, b, c)) { + val idx = r * cols + cIdx + val valInterp = barycentricInterpolateLegacy(pt, a, b, c, triVertexVals) + cells[idx] = valInterp + } + } + } + } + + val grid = GridModel( + minX = minX, + minY = minY, + maxX = maxX, + maxY = maxY, + rows = rows, + cols = cols, + cellSize = cellSize, + cells = cells + ) + return grid +} diff --git a/android/src/main/java/com/icegps/geotools/MainActivity.kt b/android/src/main/java/com/icegps/geotools/MainActivity.kt new file mode 100644 index 0000000..2cc6abd --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/MainActivity.kt @@ -0,0 +1,265 @@ +package com.icegps.geotools + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.google.android.material.slider.RangeSlider +import com.google.android.material.slider.Slider +import com.icegps.common.helper.GeoHelper +import com.icegps.geotools.databinding.ActivityMainBinding +import com.icegps.math.geometry.Angle +import com.icegps.math.geometry.degrees +import com.icegps.shared.model.GeoPoint +import com.mapbox.geojson.Point +import com.mapbox.maps.CameraOptions +import com.mapbox.maps.MapView +import com.mapbox.maps.plugin.gestures.addOnMapClickListener +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + private lateinit var mapView: MapView + private val viewModel: MainViewModel by lazy { + ViewModelProvider(this)[MainViewModel::class.java] + } + private lateinit var contoursManager: ContoursManager + private lateinit var earthworkManager: EarthworkManager + + init { + initGeoHelper() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + binding = ActivityMainBinding.inflate(layoutInflater) + mapView = binding.mapView + earthworkManager = EarthworkManager(mapView, lifecycleScope) + setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets + } + + mapView.mapboxMap.setCamera( + CameraOptions.Builder() + .center(Point.fromLngLat(home.longitude, home.latitude)) + .pitch(0.0) + .zoom(18.0) + .bearing(0.0) + .build() + ) + + val points = coordinateGenerate() + + // divider + contoursManager = ContoursManager( + context = this, + mapView = mapView, + scope = lifecycleScope + ) + contoursManager.updateContourSize(6) + contoursManager.updatePoints(points) + val height = points.map { it.z } + val min = height.min() + val max = height.max() + contoursManager.updateHeightRange((min / 2)..max) + binding.heightRange.values = listOf(min.toFloat() / 2, max.toFloat()) + binding.heightRange.valueFrom = min.toFloat() + binding.heightRange.valueTo = max.toFloat() + contoursManager.refresh() + + binding.sliderTargetHeight.addOnSliderTouchListener( + object : Slider.OnSliderTouchListener { + override fun onStartTrackingTouch(p0: Slider) { + } + + override fun onStopTrackingTouch(p0: Slider) { + val present = p0.value / p0.valueTo + // val targetHeight = ((valueRange.endInclusive - valueRange.start) * present) + valueRange.start + + // val contours = findContours(triangles, targetHeight) + // contoursTest.clearContours() + // if (false) contoursTest.updateContours(contours) + } + } + ) + + binding.heightRange.addOnSliderTouchListener( + object : RangeSlider.OnSliderTouchListener { + override fun onStartTrackingTouch(slider: RangeSlider) { + } + + override fun onStopTrackingTouch(slider: RangeSlider) { + contoursManager.updateHeightRange((slider.values.min().toDouble() - 1.0)..(slider.values.max().toDouble() + 1.0)) + contoursManager.refresh() + } + } + ) + + binding.switchGrid.setOnCheckedChangeListener { _, isChecked -> + contoursManager.setGridVisible(isChecked) + } + binding.switchTriangle.setOnCheckedChangeListener { _, isChecked -> + contoursManager.setTriangleVisible(isChecked) + } + binding.update.setOnClickListener { + contoursManager.refresh() + } + binding.cellSize.addOnSliderTouchListener( + object : Slider.OnSliderTouchListener { + override fun onStartTrackingTouch(slider: Slider) { + } + + override fun onStopTrackingTouch(slider: Slider) { + contoursManager.updateCellSize(slider.value.toDouble()) + contoursManager.refresh() + } + } + ) + mapView.mapboxMap.addOnMapClickListener { + viewModel.addPoint(it) + true + } + binding.clearPoints.setOnClickListener { + viewModel.clearPoints() + } + binding.slopeDirection.addOnSliderTouchListener( + object : Slider.OnSliderTouchListener { + override fun onStartTrackingTouch(slider: Slider) { + } + + override fun onStopTrackingTouch(slider: Slider) { + earthworkManager.updateSlopeDirection(slider.value.degrees) + } + } + ) + binding.slopePercentage.addOnSliderTouchListener( + object : Slider.OnSliderTouchListener { + override fun onStartTrackingTouch(slider: Slider) { + } + + override fun onStopTrackingTouch(slider: Slider) { + earthworkManager.updateSlopePercentage(slider.value.toDouble()) + } + } + ) + binding.designHeight.addOnSliderTouchListener( + object : Slider.OnSliderTouchListener { + override fun onStartTrackingTouch(slider: Slider) { + } + + override fun onStopTrackingTouch(slider: Slider) { + earthworkManager.updateDesignHeight(slider.value.toDouble()) + } + } + ) + binding.switchDesignSurface.setOnCheckedChangeListener { button, isChecked -> + showDesignHeight.value = isChecked + } + earthworkManager.setupOnMoveListener() + binding.switchSlopeResult.setOnCheckedChangeListener { _, isChecked -> + slopeResultVisible.value = isChecked + } + initData() + } + + private val showDesignHeight = MutableStateFlow(false) + private val slopeResultVisible = MutableStateFlow(false) + + @OptIn(ExperimentalUuidApi::class) + private fun initData() { + viewModel.points.onEach { + contoursManager.updatePoints(it) + contoursManager.updateHeightRange() + contoursManager.refresh() + }.launchIn(lifecycleScope) + contoursManager.gridModel.filterNotNull().onEach { + earthworkManager.updateGridModel(it) + }.launchIn(lifecycleScope) + earthworkManager.slopeDirection.onEach { + binding.slopeDirection.value = it.degrees.toFloat() + }.launchIn(lifecycleScope) + val slopeResultSourceId: String = Uuid.random().toString() + val slopeResultLayerId: String = Uuid.random().toString() + combine( + earthworkManager.slopeDirection, + earthworkManager.slopePercentage, + earthworkManager.baseHeightOffset, + contoursManager.gridModel, + showDesignHeight, + slopeResultVisible + ) { + Params6( + p1 = it[0] as Angle, + p2 = it[1] as Double, + p3 = it[2] as Double, + p4 = it[3] as? GridModel?, + p5 = it[4] as Boolean, + p6 = it[5] as Boolean + ) + }.map { (slopeDirection, slopePercentage, baseHeightOffset, gridModel, showDesignHeight, slopeResultVisible) -> + if (!slopeResultVisible) { + mapView.mapboxMap.getStyle { style -> + style.removeStyleLayer(slopeResultLayerId) + style.removeStyleLayer("${slopeResultLayerId}-outline") + style.removeStyleSource(slopeResultSourceId) + } + } else gridModel?.let { gridModel -> + val slopeResult: SlopeResult = SlopeCalculator.calculateSlope( + grid = gridModel, + slopeDirection = slopeDirection.degrees, + slopePercentage = slopePercentage, + baseHeightOffset = baseHeightOffset + ) + mapView.displaySlopeResult( + originalGrid = gridModel, + slopeResult = slopeResult, + sourceId = slopeResultSourceId, + layerId = slopeResultLayerId, + palette = contoursManager.simplePalette::palette, + showDesignHeight = showDesignHeight + ) + } + }.launchIn(lifecycleScope) + } +} + +data class Params6< + out P1, + out P2, + out P3, + out P4, + out P5, + out P6, + >( + val p1: P1, + val p2: P2, + val p3: P3, + val p4: P4, + val p5: P5, + val p6: P6, +) + +val home = GeoPoint(114.476060, 22.771073, 30.897) + +fun initGeoHelper(base: GeoPoint = home) { + val geoHelper = GeoHelper.getSharedInstance() + geoHelper.wgs84ToENU( + lon = base.longitude, + lat = base.latitude, + hgt = base.altitude + ) +} diff --git a/android/src/main/java/com/icegps/geotools/MainViewModel.kt b/android/src/main/java/com/icegps/geotools/MainViewModel.kt new file mode 100644 index 0000000..c33ff66 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/MainViewModel.kt @@ -0,0 +1,59 @@ +package com.icegps.geotools + +import android.app.Application +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.icegps.common.helper.GeoHelper +import com.icegps.math.geometry.Vector3D +import com.icegps.geotools.ktx.toast +import com.icegps.shared.SharedHttpClient +import com.icegps.shared.SharedJson +import com.icegps.shared.api.OpenElevation +import com.icegps.shared.api.OpenElevationApi +import com.icegps.shared.ktx.TAG +import com.icegps.shared.model.GeoPoint +import com.mapbox.geojson.Point +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update + +class MainViewModel(private val context: Application) : AndroidViewModel(context) { + private val geoHelper = GeoHelper.Companion.getSharedInstance() + private val openElevation: OpenElevationApi = OpenElevation(SharedHttpClient(SharedJson())) + + private val _points = MutableStateFlow>(emptyList()) + val points = _points.filter { it.size > 3 }.debounce(1000).map { + openElevation.lookup(it.map { GeoPoint(it.longitude(), it.latitude(), it.altitude()) }) + }.catch { + Log.e(TAG, "高程请求失败", it) + context.toast("高程请求失败") + }.map { + it.map { + val enu = geoHelper.wgs84ToENU(lon = it.longitude, lat = it.latitude, hgt = it.altitude) + Vector3D(enu.x, enu.y, enu.z) + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.Companion.Eagerly, + initialValue = emptyList() + ) + + fun addPoint(point: Point) { + context.toast("${point.longitude()}, ${point.latitude()}") + _points.update { + it.toMutableList().apply { + add(point) + } + } + } + + fun clearPoints() { + _points.value = emptyList() + } +} \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/PolylineManager.kt b/android/src/main/java/com/icegps/geotools/PolylineManager.kt new file mode 100644 index 0000000..6ae531e --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/PolylineManager.kt @@ -0,0 +1,121 @@ +package com.icegps.geotools + +import android.graphics.Color +import com.icegps.math.geometry.Line3D +import com.icegps.math.geometry.Vector3D +import com.icegps.geotools.ktx.toMapboxPoint +import com.mapbox.geojson.Feature +import com.mapbox.geojson.FeatureCollection +import com.mapbox.geojson.LineString +import com.mapbox.maps.MapView +import com.mapbox.maps.Style +import com.mapbox.maps.extension.style.layers.addLayer +import com.mapbox.maps.extension.style.layers.generated.lineLayer +import com.mapbox.maps.extension.style.layers.properties.generated.LineCap +import com.mapbox.maps.extension.style.layers.properties.generated.LineJoin +import com.mapbox.maps.extension.style.sources.addSource +import com.mapbox.maps.extension.style.sources.generated.geoJsonSource + +class PolylineManager( + private val mapView: MapView +) { + private val sourceId: String = "polyline-source-id-0" + private val layerId: String = "polyline-layer-id-0" + + fun update( + points: List> + ) { + val lineStrings: List> = points.map { + val lines = fromPoints(it, true) + lines.map { + LineString.fromLngLats(listOf(it.a.toMapboxPoint(), it.b.toMapboxPoint())) + } + }.map { + it.map { Feature.fromGeometry(it) } + } + + mapView.mapboxMap.getStyle { style -> + setupLineLayer( + style = style, + sourceId = sourceId, + layerId = layerId, + features = lineStrings.flatten() + ) + } + } + + fun updateFeatures( + features: List + ) { + mapView.mapboxMap.getStyle { style -> + setupLineLayer( + style = style, + sourceId = sourceId, + layerId = layerId, + features = features + ) + } + } + + private fun setupLineLayer( + style: Style, + sourceId: String, + layerId: String, + features: List + ) { + style.removeStyleLayer(layerId) + style.removeStyleSource(sourceId) + + val source = geoJsonSource(sourceId) { + featureCollection(FeatureCollection.fromFeatures(features)) + } + style.addSource(source) + + val layer = lineLayer(layerId, sourceId) { + lineColor(Color.RED) + lineWidth(2.0) + lineCap(LineCap.Companion.ROUND) + lineJoin(LineJoin.Companion.ROUND) + lineOpacity(0.8) + } + style.addLayer(layer) + } + + fun clearContours() { + mapView.mapboxMap.getStyle { style -> + try { + style.removeStyleLayer(layerId) + } catch (_: Exception) { + } + try { + style.removeStyleSource(sourceId) + } catch (_: Exception) { + } + } + } +} + +fun fromPoints( + points: List, + closed: Boolean, +) = if (points.isEmpty()) { + emptyList() +} else { + if (!closed) { + (0 until points.size - 1).map { + Line3D( + points[it], + points[it + 1] + ) + } + } else { + val d = (points.last() - points.first()).length + val usePoints = if (d > 1E-6) points else points.dropLast(1) + (usePoints.indices).map { + Line3D( + usePoints[it], + usePoints[(it + 1) % usePoints.size] + ) + } + } +} diff --git a/android/src/main/java/com/icegps/geotools/RayCastingAlgorithm.kt b/android/src/main/java/com/icegps/geotools/RayCastingAlgorithm.kt new file mode 100644 index 0000000..bb81f3c --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/RayCastingAlgorithm.kt @@ -0,0 +1,44 @@ +package com.icegps.geotools + +import com.icegps.math.geometry.Vector2D +import com.icegps.math.geometry.Vector3D +import com.icegps.math.geometry.toVector2D + +/** + * @author tabidachinokaze + * @date 2025/11/26 + */ +object RayCastingAlgorithm { + /** + * 使用射线法判断点是否在多边形内 + * @param point 测试点 + * @param polygon 多边形顶点列表 + * @return true如果在多边形内 + */ + fun isPointInPolygon(point: Vector2D, polygon: List): Boolean { + if (polygon.size < 3) return false + + val x = point.x + val y = point.y + var inside = false + + var j = polygon.size - 1 + for (i in polygon.indices) { + val xi = polygon[i].x + val yi = polygon[i].y + val xj = polygon[j].x + val yj = polygon[j].y + + val intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi) + + if (intersect) inside = !inside + j = i + } + + return inside + } + + fun isPointInPolygon(point: Vector3D, polygon: List): Boolean { + return isPointInPolygon(point.toVector2D(), polygon.map { it.toVector2D() }) + } +} \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/SimplePalette.kt b/android/src/main/java/com/icegps/geotools/SimplePalette.kt new file mode 100644 index 0000000..9e41de0 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/SimplePalette.kt @@ -0,0 +1,123 @@ +package com.icegps.geotools + +import android.util.Log + +/** + * @author tabidachinokaze + * @date 2025/11/25 + */ +class SimplePalette( + private var range: ClosedFloatingPointRange +) { + fun setRange(range: ClosedFloatingPointRange) { + this.range = range + } + + private val colors: Map + + init { + colors = generateTerrainColorMap() + } + + fun palette(value: Double?): String { + if (value == null) return "#00000000" + val minH = range.start + val maxH = range.endInclusive + val normalized = ((value - minH) / (maxH - minH)).coerceIn(0.0, 1.0) + return colors[(normalized * 255).toInt()] ?: "#00000000" + } + + fun palette1(value: Double?): String { + return if (value == null) "#00000000" else { + // 假设您已经知道高度范围,或者动态计算 + val minH = range.start + val maxH = range.endInclusive + val normalized = ((value - minH) / (maxH - minH)).coerceIn(0.0, 1.0) + val alpha = (normalized * 255).toInt() + String.format("#%02X%02X%02X", alpha, 0, 0) + }.also { + Log.d("simplePalette", "$value -> $it") + } + } + + fun generateGrayscaleColorMap2(): MutableMap { + val colorMap = mutableMapOf() + + // 定义关键灰度点 + val black = Color(0, 0, 0) // 低地势 - 黑色 + val darkGray = Color(64, 64, 64) // 过渡 + val midGray = Color(128, 128, 128) // 中间 + val lightGray = Color(192, 192, 192) // 过渡 + val white = Color(255, 255, 255) // 高地势 - 白色 + + for (i in 0..255) { + val position = i / 255.0 + + val color = when { + position < 0.25 -> interpolateColor(black, darkGray, position / 0.25) + position < 0.5 -> interpolateColor(darkGray, midGray, (position - 0.25) / 0.25) + position < 0.75 -> interpolateColor(midGray, lightGray, (position - 0.5) / 0.25) + else -> interpolateColor(lightGray, white, (position - 0.75) / 0.25) + } + colorMap[i] = color.toHex() + } + + return colorMap + } + + fun generateGrayscaleColorMap(): MutableMap { + val colorMap = mutableMapOf() + + for (i in 0..255) { + // 从黑色到白色的线性渐变 + val grayValue = i + val color = Color(grayValue, grayValue, grayValue) + colorMap[i] = color.toHex() + } + + return colorMap + } + + fun generateTerrainColorMap(): MutableMap { + val colorMap = mutableMapOf() + + // 定义关键颜色点 + val blue = Color(0, 0, 255) // 低地势 - 蓝色 + val cyan = Color(0, 255, 255) // 中间过渡 + val green = Color(0, 255, 0) // 中间过渡 + val yellow = Color(255, 255, 0) // 中间过渡 + val red = Color(255, 0, 0) // 高地势 - 红色 + + for (i in 0..255) { + val position = i / 255.0 + + val color = when { + position < 0.25 -> interpolateColor(blue, cyan, position / 0.25) + position < 0.5 -> interpolateColor(cyan, green, (position - 0.25) / 0.25) + position < 0.75 -> interpolateColor(green, yellow, (position - 0.5) / 0.25) + else -> interpolateColor(yellow, red, (position - 0.75) / 0.25) + } + colorMap[i] = color.toHex() + } + + return colorMap + } + + fun interpolateColor(start: Color, end: Color, fraction: Double): Color { + val r = (start.red + (end.red - start.red) * fraction).toInt() + val g = (start.green + (end.green - start.green) * fraction).toInt() + val b = (start.blue + (end.blue - start.blue) * fraction).toInt() + return Color(r, g, b) + } + + // Color类简化实现 + class Color(val red: Int, val green: Int, val blue: Int) { + fun toArgb(): Int { + return (0xFF shl 24) or (red shl 16) or (green shl 8) or blue + } + + fun toHex(): String { + return String.format("#%06X", toArgb() and 0xFFFFFF) + } + } +} \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/catmullrom/CatmullRom.kt b/android/src/main/java/com/icegps/geotools/catmullrom/CatmullRom.kt new file mode 100644 index 0000000..90ac451 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/catmullrom/CatmullRom.kt @@ -0,0 +1,135 @@ +package com.icegps.geotools.catmullrom + +import com.icegps.math.geometry.Vector2D +import com.icegps.geotools.marchingsquares.Segment2D +import com.icegps.geotools.marchingsquares.ShapeContour +import kotlin.math.min +import kotlin.math.pow + +private const val almostZero = 0.00000001 +private const val almostOne = 0.99999999 + +/** + * Creates a 2D Catmull-Rom spline curve. + * + * Can be represented as a segment drawn between [p1] and [p2], + * while [p0] and [p3] are used as control points. + * + * Under some circumstances alpha can have + * no perceptible effect, for example, + * when creating closed shapes with the vertices + * forming a regular 2D polygon. + * + * @param p0 The first control point. + * @param p1 The starting anchor point. + * @param p2 The ending anchor point. + * @param p3 The second control point. + * @param alpha The *tension* of the curve. + * Use `0.0` for the uniform spline, `0.5` for the centripetal spline, `1.0` for the chordal spline. + */ +class CatmullRom2(val p0: Vector2D, val p1: Vector2D, val p2: Vector2D, val p3: Vector2D, val alpha: Double = 0.5) { + /** Value of t for p0. */ + val t0: Double = 0.0 + + /** Value of t for p1. */ + val t1: Double = calculateT(t0, p0, p1) + + /** Value of t for p2. */ + val t2: Double = calculateT(t1, p1, p2) + + /** Value of t for p3. */ + val t3: Double = calculateT(t2, p2, p3) + + fun position(rt: Double): Vector2D { + val t = t1 + rt * (t2 - t1) + val a1 = p0 * ((t1 - t) / (t1 - t0)) + p1 * ((t - t0) / (t1 - t0)) + val a2 = p1 * ((t2 - t) / (t2 - t1)) + p2 * ((t - t1) / (t2 - t1)) + val a3 = p2 * ((t3 - t) / (t3 - t2)) + p3 * ((t - t2) / (t3 - t2)) + + val b1 = a1 * ((t2 - t) / (t2 - t0)) + a2 * ((t - t0) / (t2 - t0)) + val b2 = a2 * ((t3 - t) / (t3 - t1)) + a3 * ((t - t1) / (t3 - t1)) + + val c = b1 * ((t2 - t) / (t2 - t1)) + b2 * ((t - t1) / (t2 - t1)) + return c + } + + private fun calculateT(t: Double, p0: Vector2D, p1: Vector2D): Double { + val a = (p1.x - p0.x).pow(2.0) + (p1.y - p0.y).pow(2.0) + val b = a.pow(0.5) + val c = b.pow(alpha) + return c + t + } +} + +/** + * Calculates the 2D Catmull–Rom spline for a chain of points and returns the combined curve. + * + * For more details, see [CatmullRom2]. + * + * @param points The [List] of 2D points where [CatmullRom2] is applied in groups of 4. + * @param alpha The *tension* of the curve. + * Use `0.0` for the uniform spline, `0.5` for the centripetal spline, `1.0` for the chordal spline. + * @param loop Whether to connect the first and last point, such that it forms a closed shape. + */ +class CatmullRomChain2(points: List, alpha: Double = 0.5, val loop: Boolean = false) { + val segments = if (!loop) { + val startPoints = points.take(2) + val endPoints = points.takeLast(2) + val mirrorStart = + startPoints.first() - (startPoints.last() - startPoints.first()).normalized + val mirrorEnd = endPoints.last() + (endPoints.last() - endPoints.first()).normalized + + (listOf(mirrorStart) + points + listOf(mirrorEnd)).windowed(4, 1).map { + CatmullRom2(it[0], it[1], it[2], it[3], alpha) + } + } else { + val cleanPoints = if (loop && points.first().distanceTo(points.last()) <= 1.0E-6) { + points.dropLast(1) + } else { + points + } + (cleanPoints + cleanPoints.take(3)).windowed(4, 1).map { + CatmullRom2(it[0], it[1], it[2], it[3], alpha) + } + } + + fun positions(steps: Int = segments.size * 4): List { + return (0..steps).map { + position(it.toDouble() / steps) + } + } + + fun position(rt: Double): Vector2D { + val st = if (loop) rt.mod(1.0) else rt.coerceIn(0.0, 1.0) + val segmentIndex = (min(almostOne, st) * segments.size).toInt() + val t = (min(almostOne, st) * segments.size) - segmentIndex + return segments[segmentIndex].position(t) + } +} + +fun List.catmullRom(alpha: Double = 0.5, closed: Boolean) = CatmullRomChain2(this, alpha, closed) + +/** Converts spline to a [Segment]. */ +fun CatmullRom2.toSegment(): Segment2D { + val d1a2 = (p1 - p0).length.pow(2 * alpha) + val d2a2 = (p2 - p1).length.pow(2 * alpha) + val d3a2 = (p3 - p2).length.pow(2 * alpha) + val d1a = (p1 - p0).length.pow(alpha) + val d2a = (p2 - p1).length.pow(alpha) + val d3a = (p3 - p2).length.pow(alpha) + + val b0 = p1 + val b1 = (p2 * d1a2 - p0 * d2a2 + p1 * (2 * d1a2 + 3 * d1a * d2a + d2a2)) / (3 * d1a * (d1a + d2a)) + val b2 = (p1 * d3a2 - p3 * d2a2 + p2 * (2 * d3a2 + 3 * d3a * d2a + d2a2)) / (3 * d3a * (d3a + d2a)) + val b3 = p2 + + return Segment2D(b0, b1, b2, b3) +} + + +/** + * Converts chain to a [ShapeContour]. + */ +@Suppress("unused") +fun CatmullRomChain2.toContour(): ShapeContour = + ShapeContour(segments.map { it.toSegment() }, this.loop) diff --git a/android/src/main/java/com/icegps/geotools/color/ColorRGBa.kt b/android/src/main/java/com/icegps/geotools/color/ColorRGBa.kt new file mode 100644 index 0000000..1f667d3 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/color/ColorRGBa.kt @@ -0,0 +1,427 @@ +package com.icegps.geotools.color + +import com.icegps.math.geometry.Vector3D +import com.icegps.math.geometry.Vector4D +import kotlinx.serialization.Serializable +import kotlin.math.pow + +@Serializable +enum class Linearity(val certainty: Int) { + /** + * Represents a linear color space. + * + * LINEAR typically signifies that the values in the color space are in a linear relationship, + * meaning there is no gamma correction or transformation applied to the data. + */ + LINEAR(1), + + /** + * Represents a standard RGB (sRGB) color space. + * + * SRGB typically refers to a non-linear color space with gamma correction applied, + * designed for consistent color representation across devices. + */ + SRGB(1), + ; + + fun leastCertain(other: Linearity): Linearity { + return if (this.certainty <= other.certainty) { + this + } else { + other + } + } + + fun isEquivalent(other: Linearity): Boolean { + return this == other + } + +} + +/** + * Represents a color in the RGBA color space. Each component, including red, green, blue, and alpha (opacity), + * is represented as a `Double` in the range `[0.0, 1.0]`. The color can be defined in either linear or sRGB space, + * determined by the `linearity` property. + * + * This class provides a wide variety of utility functions for manipulating and converting colors, such as shading, + * opacity adjustment, and format transformations. It also includes methods for parsing colors from hexadecimal + * notation or vectors. + * + * @property r Red component of the color as a value between `0.0` and `1.0`. + * @property g Green component of the color as a value between `0.0` and `1.0`. + * @property b Blue component of the color as a value between `0.0` and `1.0`. + * @property alpha Alpha (opacity) component of the color as a value between `0.0` and `1.0`. Defaults to `1.0`. + * @property linearity Indicates whether the color is defined in linear or sRGB space. Defaults to [Linearity.LINEAR]. + */ +@Serializable +@Suppress("EqualsOrHashCode") // generated equals() is ok, only hashCode() needs to be overridden +data class ColorRGBa( + val r: Double, + val g: Double, + val b: Double, + val alpha: Double = 1.0, + val linearity: Linearity = Linearity.LINEAR +) { + + enum class Component { + R, + G, + B + } + + companion object { + /** + * Calculates a color from hexadecimal value. For values with transparency + * use the [String] variant of this function. + */ + fun fromHex(hex: Int): ColorRGBa { + val r = hex and (0xff0000) shr 16 + val g = hex and (0x00ff00) shr 8 + val b = hex and (0x0000ff) + return ColorRGBa(r / 255.0, g / 255.0, b / 255.0, 1.0, Linearity.SRGB) + } + + /** + * Calculates a color from hexadecimal notation, like in CSS. + * + * Supports the following formats + * * `RGB` + * * `RGBA` + * * `RRGGBB` + * * `RRGGBBAA` + * + * where every character is a valid hex digit between `0..f` (case-insensitive). + * Supports leading "#" or "0x". + */ + fun fromHex(hex: String): ColorRGBa { + val pos = when { + hex.startsWith("#") -> 1 + hex.startsWith("0x") -> 2 + else -> 0 + } + + fun fromHex1(str: String, pos: Int): Double { + return 17 * str[pos].digitToInt(16) / 255.0 + } + + fun fromHex2(str: String, pos: Int): Double { + return (16 * str[pos].digitToInt(16) + str[pos + 1].digitToInt(16)) / 255.0 + } + return when (hex.length - pos) { + 3 -> ColorRGBa(fromHex1(hex, pos), fromHex1(hex, pos + 1), fromHex1(hex, pos + 2), 1.0, Linearity.SRGB) + 4 -> ColorRGBa( + fromHex1(hex, pos), + fromHex1(hex, pos + 1), + fromHex1(hex, pos + 2), + fromHex1(hex, pos + 3), + Linearity.SRGB + ) + + 6 -> ColorRGBa(fromHex2(hex, pos), fromHex2(hex, pos + 2), fromHex2(hex, pos + 4), 1.0, Linearity.SRGB) + 8 -> ColorRGBa( + fromHex2(hex, pos), + fromHex2(hex, pos + 2), + fromHex2(hex, pos + 4), + fromHex2(hex, pos + 6), + Linearity.SRGB + ) + + else -> throw IllegalArgumentException("Invalid hex length/format for '$hex'") + } + } + + /** @suppress */ + val PINK = fromHex(0xffc0cb) + + /** @suppress */ + val BLACK = ColorRGBa(0.0, 0.0, 0.0, 1.0, Linearity.SRGB) + + /** @suppress */ + val WHITE = ColorRGBa(1.0, 1.0, 1.0, 1.0, Linearity.SRGB) + + /** @suppress */ + val RED = ColorRGBa(1.0, 0.0, 0.0, 1.0, Linearity.SRGB) + + /** @suppress */ + val BLUE = ColorRGBa(0.0, 0.0, 1.0, 1.0, Linearity.SRGB) + + /** @suppress */ + val GREEN = ColorRGBa(0.0, 1.0, 0.0, 1.0, Linearity.SRGB) + + /** @suppress */ + val YELLOW = ColorRGBa(1.0, 1.0, 0.0, 1.0, Linearity.SRGB) + + /** @suppress */ + val CYAN = ColorRGBa(0.0, 1.0, 1.0, 1.0, Linearity.SRGB) + + /** @suppress */ + val MAGENTA = ColorRGBa(1.0, 0.0, 1.0, 1.0, Linearity.SRGB) + + /** @suppress */ + val GRAY = ColorRGBa(0.5, 0.5, 0.5, 1.0, Linearity.SRGB) + + /** @suppress */ + val TRANSPARENT = ColorRGBa(0.0, 0.0, 0.0, 0.0, Linearity.LINEAR) + + /** + * Create a ColorRGBa object from a [Vector3] + * @param vector input vector, `[x, y, z]` is mapped to `[r, g, b]` + * @param alpha optional alpha value, default is 1.0 + */ + fun fromVector(vector: Vector3D, alpha: Double = 1.0, linearity: Linearity = Linearity.LINEAR): ColorRGBa { + return ColorRGBa(vector.x, vector.y, vector.z, alpha, linearity) + } + + + /** + * Create a ColorRGBa object from a [Vector4] + * @param vector input vector, `[x, y, z, w]` is mapped to `[r, g, b, a]` + */ + fun fromVector(vector: Vector4D, linearity: Linearity = Linearity.LINEAR): ColorRGBa { + return ColorRGBa(vector.x, vector.y, vector.z, vector.w, linearity) + } + } + + @Deprecated("Legacy alpha parameter name", ReplaceWith("alpha")) + val a = alpha + + /** + * Creates a copy of color with adjusted opacity + * @param factor a scaling factor used for the opacity + * @return A [ColorRGBa] with scaled opacity + * @see shade + */ + fun opacify(factor: Double): ColorRGBa = ColorRGBa(r, g, b, alpha * factor, linearity) + + /** + * Creates a copy of color with adjusted color + * @param factor a scaling factor used for the opacity + * @return A [ColorRGBa] with scaled colors + * @see opacify + */ + fun shade(factor: Double): ColorRGBa = ColorRGBa(r * factor, g * factor, b * factor, alpha, linearity) + + /** + * Copy of the color with all of its fields clamped to `[0, 1]` + */ + + @Deprecated("Use clip() instead", replaceWith = ReplaceWith("clip()")) + val saturated: ColorRGBa + get() = clip() + + /** + * Copy of the color with all of its fields clamped to `[0, 1]` + */ + fun clip(): ColorRGBa = copy( + r = r.coerceIn(0.0..1.0), + g = g.coerceIn(0.0..1.0), + b = b.coerceIn(0.0..1.0), + alpha = alpha.coerceIn(0.0..1.0) + ) + + + /** + * Returns a new instance of [ColorRGBa] where the red, green, and blue components + * are multiplied by the alpha value of the original color. The alpha value and linearity + * remain unchanged. + * + * This computed property is commonly used for adjusting the color intensity based + * on its transparency. + */ + val alphaMultiplied: ColorRGBa + get() = ColorRGBa(r * alpha, g * alpha, b * alpha, alpha, linearity) + + /** + * The minimum value over `r`, `g`, `b` + * @see maxValue + */ + val minValue get() = r.coerceAtMost(g).coerceAtMost(b) + + /** + * The maximum value over `r`, `g`, `b` + * @see minValue + */ + val maxValue get() = r.coerceAtLeast(g).coerceAtLeast(b) + + /** + * calculate luminance value + * luminance value is according to https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + */ + val luminance: Double + get() = when (linearity) { + Linearity.SRGB -> toLinear().luminance + else -> 0.2126 * r + 0.7152 * g + 0.0722 * b + } + + /** + * Converts this color to the specified linearity. + * + * @param linearity The target linearity to which the color should be converted. + * Supported values are [Linearity.SRGB] and [Linearity.LINEAR]. + * @return A [ColorRGBa] instance in the specified linearity. + */ + fun toLinearity(linearity: Linearity): ColorRGBa { + return when (linearity) { + Linearity.SRGB -> toSRGB() + Linearity.LINEAR -> toLinear() + } + } + + /** + * calculate the contrast value between this color and the given color + * contrast value is accordingo to // see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef + */ + fun getContrastRatio(other: ColorRGBa): Double { + val l1 = luminance + val l2 = other.luminance + return if (l1 > l2) (l1 + 0.05) / (l2 + 0.05) else (l2 + 0.05) / (l1 + 0.05) + } + + fun toLinear(): ColorRGBa { + fun t(x: Double): Double { + return if (x <= 0.04045) x / 12.92 else ((x + 0.055) / (1 + 0.055)).pow(2.4) + } + return when (linearity) { + Linearity.SRGB -> ColorRGBa(t(r), t(g), t(b), alpha, Linearity.LINEAR) + else -> this + } + } + + /** + * Convert to SRGB + * @see toLinear + */ + fun toSRGB(): ColorRGBa { + fun t(x: Double): Double { + return if (x <= 0.0031308) 12.92 * x else (1 + 0.055) * x.pow(1.0 / 2.4) - 0.055 + } + return when (linearity) { + Linearity.LINEAR -> ColorRGBa(t(r), t(g), t(b), alpha, Linearity.SRGB) + else -> this + } + } + + fun toRGBa(): ColorRGBa = this + + // This is here because the default hashing of enums on the JVM is not stable. + override fun hashCode(): Int { + var result = r.hashCode() + result = 31 * result + g.hashCode() + result = 31 * result + b.hashCode() + result = 31 * result + alpha.hashCode() + // here we overcome the unstable hash by using the ordinal value + result = 31 * result + linearity.ordinal.hashCode() + return result + } + + fun plus(right: ColorRGBa) = copy( + r = r + right.r, + g = g + right.g, + b = b + right.b, + alpha = alpha + right.alpha + ) + + fun minus(right: ColorRGBa) = copy( + r = r - right.r, + g = g - right.g, + b = b - right.b, + alpha = alpha - right.alpha + ) + + fun times(scale: Double) = copy(r = r * scale, g = g * scale, b = b * scale, alpha = alpha * scale) + + fun mix(other: ColorRGBa, factor: Double): ColorRGBa { + return mix(this, other, factor) + } + + fun toVector4(): Vector4D = Vector4D(r, g, b, alpha) + + /** + * Retrieves the color's RGBA component value based on the specified index: + * [index] should be 0 for red, 1 for green, 2 for blue, 3 for alpha. + * Other index values throw an [IndexOutOfBoundsException]. + */ + operator fun get(index: Int) = when (index) { + 0 -> r + 1 -> g + 2 -> b + 3 -> alpha + else -> throw IllegalArgumentException("unsupported index") + } +} + +/** + * Weighted mix between two colors in the generic RGB color space. + * @param x the weighting of colors, a value 0.0 is equivalent to [left], + * 1.0 is equivalent to [right] and at 0.5 both colors contribute to the result equally + * @return a mix of [left] and [right] weighted by [x] + */ +fun mix(left: ColorRGBa, right: ColorRGBa, x: Double): ColorRGBa { + val sx = x.coerceIn(0.0, 1.0) + + if (left.linearity.isEquivalent(right.linearity)) { + return ColorRGBa( + (1.0 - sx) * left.r + sx * right.r, + (1.0 - sx) * left.g + sx * right.g, + (1.0 - sx) * left.b + sx * right.b, + (1.0 - sx) * left.alpha + sx * right.alpha, + linearity = left.linearity.leastCertain(right.linearity) + ) + } else { + return when (right.linearity) { + Linearity.LINEAR -> { + mix(left.toLinear(), right.toLinear(), x) + } + + Linearity.SRGB -> { + mix(left.toSRGB(), right.toSRGB(), x) + } + } + } +} + +/** + * Shorthand for calling [ColorRGBa]. + * Specify only one value to obtain a shade of gray. + * @param r red in `[0,1]` + * @param g green in `[0,1]` + * @param b blue in `[0,1]` + * @param a alpha in `[0,1]`, defaults to `1.0` + */ +fun rgb(r: Double, g: Double, b: Double, a: Double = 1.0) = ColorRGBa(r, g, b, a, linearity = Linearity.LINEAR) + +/** + * Shorthand for calling [ColorRGBa]. + * @param gray shade of gray in `[0,1]` + * @param a alpha in `[0,1]`, defaults to `1.0` + */ +fun rgb(gray: Double, a: Double = 1.0) = ColorRGBa(gray, gray, gray, a, linearity = Linearity.LINEAR) + +/** + * Create a color in RGBa space + * This function is a shorthand for using the ColorRGBa constructor + * @param r red in `[0,1]` + * @param g green in `[0,1]` + * @param b blue in `[0,1]` + * @param a alpha in `[0,1]` + */ +@Deprecated("Use rgb(r, g, b, a)", ReplaceWith("rgb(r, g, b, a)"), DeprecationLevel.WARNING) +fun rgba(r: Double, g: Double, b: Double, a: Double) = ColorRGBa(r, g, b, a, linearity = Linearity.LINEAR) + +/** + * Shorthand for calling [ColorRGBa.fromHex]. + * Creates a [ColorRGBa] with [Linearity.SRGB] from a hex string. + * @param hex string encoded hex value, for example `"ffc0cd"` + */ +fun rgb(hex: String) = ColorRGBa.fromHex(hex) + +/** + * Converts RGB integer color values into a ColorRGBa object with sRGB linearity. + * + * @param red The red component of the color, in the range 0-255. + * @param green The green component of the color, in the range 0-255. + * @param blue The blue component of the color, in the range 0-255. + * @param alpha The alpha (transparency) component of the color, in the range 0-255. Default value is 255 (fully opaque). + */ +fun rgb(red: Int, green: Int, blue: Int, alpha: Int = 255) = + ColorRGBa(red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0, Linearity.SRGB) diff --git a/android/src/main/java/com/icegps/geotools/colorbrewer2/ColorBrewer2.kt b/android/src/main/java/com/icegps/geotools/colorbrewer2/ColorBrewer2.kt new file mode 100644 index 0000000..2c28d0b --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/colorbrewer2/ColorBrewer2.kt @@ -0,0 +1,2777 @@ +import com.icegps.geotools.color.ColorRGBa +import com.icegps.geotools.color.rgb + +/** + * # ColorBrewer2 + * + * https://colorbrewer2.org/ + * + * Based on the research of Dr. Cynthia Brewer. + */ + +enum class ColorBrewer2Type { + Any, Diverging, Qualitative, Sequential +} + +class ColorBrewer2Palette(val colors: List, val type: ColorBrewer2Type) + +val colorBrewer2 = listOf( + ColorBrewer2Palette( + listOf( + rgb(0.9882, 0.5529, 0.3490), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.6000, 0.8353, 0.5804) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.0980, 0.1098), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.6706, 0.8667, 0.6431), + rgb(0.1686, 0.5137, 0.7294) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.0980, 0.1098), + rgb(0.9922, 0.6824, 0.3804), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.6706, 0.8667, 0.6431), + rgb(0.1686, 0.5137, 0.7294) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8353, 0.2431, 0.3098), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9961, 0.8784, 0.5451), + rgb(0.9020, 0.9608, 0.5961), + rgb(0.6000, 0.8353, 0.5804), + rgb(0.1961, 0.5333, 0.7412) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8353, 0.2431, 0.3098), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9961, 0.8784, 0.5451), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.9020, 0.9608, 0.5961), + rgb(0.6000, 0.8353, 0.5804), + rgb(0.1961, 0.5333, 0.7412) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8353, 0.2431, 0.3098), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5451), + rgb(0.9020, 0.9608, 0.5961), + rgb(0.6706, 0.8667, 0.6431), + rgb(0.4000, 0.7608, 0.6471), + rgb(0.1961, 0.5333, 0.7412) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8353, 0.2431, 0.3098), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5451), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.9020, 0.9608, 0.5961), + rgb(0.6706, 0.8667, 0.6431), + rgb(0.4000, 0.7608, 0.6471), + rgb(0.1961, 0.5333, 0.7412) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6196, 0.0039, 0.2588), + rgb(0.8353, 0.2431, 0.3098), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5451), + rgb(0.9020, 0.9608, 0.5961), + rgb(0.6706, 0.8667, 0.6431), + rgb(0.4000, 0.7608, 0.6471), + rgb(0.1961, 0.5333, 0.7412), + rgb(0.3686, 0.3098, 0.6353) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6196, 0.0039, 0.2588), + rgb(0.8353, 0.2431, 0.3098), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5451), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.9020, 0.9608, 0.5961), + rgb(0.6706, 0.8667, 0.6431), + rgb(0.4000, 0.7608, 0.6471), + rgb(0.1961, 0.5333, 0.7412), + rgb(0.3686, 0.3098, 0.6353) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.9882, 0.5529, 0.3490), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.5686, 0.8118, 0.3765) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.0980, 0.1098), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.6510, 0.8510, 0.4157), + rgb(0.1020, 0.5882, 0.2549) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.0980, 0.1098), + rgb(0.9922, 0.6824, 0.3804), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.6510, 0.8510, 0.4157), + rgb(0.1020, 0.5882, 0.2549) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9961, 0.8784, 0.5451), + rgb(0.8510, 0.9373, 0.5451), + rgb(0.5686, 0.8118, 0.3765), + rgb(0.1020, 0.5961, 0.3137) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9961, 0.8784, 0.5451), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.8510, 0.9373, 0.5451), + rgb(0.5686, 0.8118, 0.3765), + rgb(0.1020, 0.5961, 0.3137) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5451), + rgb(0.8510, 0.9373, 0.5451), + rgb(0.6510, 0.8510, 0.4157), + rgb(0.4000, 0.7412, 0.3882), + rgb(0.1020, 0.5961, 0.3137) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5451), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.8510, 0.9373, 0.5451), + rgb(0.6510, 0.8510, 0.4157), + rgb(0.4000, 0.7412, 0.3882), + rgb(0.1020, 0.5961, 0.3137) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6471, 0.0000, 0.1490), + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5451), + rgb(0.8510, 0.9373, 0.5451), + rgb(0.6510, 0.8510, 0.4157), + rgb(0.4000, 0.7412, 0.3882), + rgb(0.1020, 0.5961, 0.3137), + rgb(0.0000, 0.4078, 0.2157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6471, 0.0000, 0.1490), + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5451), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.8510, 0.9373, 0.5451), + rgb(0.6510, 0.8510, 0.4157), + rgb(0.4000, 0.7412, 0.3882), + rgb(0.1020, 0.5961, 0.3137), + rgb(0.0000, 0.4078, 0.2157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.9373, 0.5412, 0.3843), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.4039, 0.6627, 0.8118) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7922, 0.0000, 0.1255), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.5725, 0.7725, 0.8706), + rgb(0.0196, 0.4431, 0.6902) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7922, 0.0000, 0.1255), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.5725, 0.7725, 0.8706), + rgb(0.0196, 0.4431, 0.6902) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6980, 0.0941, 0.1686), + rgb(0.9373, 0.5412, 0.3843), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.8196, 0.8980, 0.9412), + rgb(0.4039, 0.6627, 0.8118), + rgb(0.1294, 0.4000, 0.6745) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6980, 0.0941, 0.1686), + rgb(0.9373, 0.5412, 0.3843), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8196, 0.8980, 0.9412), + rgb(0.4039, 0.6627, 0.8118), + rgb(0.1294, 0.4000, 0.6745) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6980, 0.0941, 0.1686), + rgb(0.8392, 0.3765, 0.3020), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.8196, 0.8980, 0.9412), + rgb(0.5725, 0.7725, 0.8706), + rgb(0.2627, 0.5765, 0.7647), + rgb(0.1294, 0.4000, 0.6745) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6980, 0.0941, 0.1686), + rgb(0.8392, 0.3765, 0.3020), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8196, 0.8980, 0.9412), + rgb(0.5725, 0.7725, 0.8706), + rgb(0.2627, 0.5765, 0.7647), + rgb(0.1294, 0.4000, 0.6745) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4039, 0.0000, 0.1216), + rgb(0.6980, 0.0941, 0.1686), + rgb(0.8392, 0.3765, 0.3020), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.8196, 0.8980, 0.9412), + rgb(0.5725, 0.7725, 0.8706), + rgb(0.2627, 0.5765, 0.7647), + rgb(0.1294, 0.4000, 0.6745), + rgb(0.0196, 0.1882, 0.3804) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4039, 0.0000, 0.1216), + rgb(0.6980, 0.0941, 0.1686), + rgb(0.8392, 0.3765, 0.3020), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8196, 0.8980, 0.9412), + rgb(0.5725, 0.7725, 0.8706), + rgb(0.2627, 0.5765, 0.7647), + rgb(0.1294, 0.4000, 0.6745), + rgb(0.0196, 0.1882, 0.3804) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.9137, 0.6392, 0.7882), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.6314, 0.8431, 0.4157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8157, 0.1098, 0.5451), + rgb(0.9451, 0.7137, 0.8549), + rgb(0.7216, 0.8824, 0.5255), + rgb(0.3020, 0.6745, 0.1490) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8157, 0.1098, 0.5451), + rgb(0.9451, 0.7137, 0.8549), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.7216, 0.8824, 0.5255), + rgb(0.3020, 0.6745, 0.1490) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7725, 0.1059, 0.4902), + rgb(0.9137, 0.6392, 0.7882), + rgb(0.9922, 0.8784, 0.9373), + rgb(0.9020, 0.9608, 0.8157), + rgb(0.6314, 0.8431, 0.4157), + rgb(0.3020, 0.5725, 0.1294) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7725, 0.1059, 0.4902), + rgb(0.9137, 0.6392, 0.7882), + rgb(0.9922, 0.8784, 0.9373), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.9020, 0.9608, 0.8157), + rgb(0.6314, 0.8431, 0.4157), + rgb(0.3020, 0.5725, 0.1294) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7725, 0.1059, 0.4902), + rgb(0.8706, 0.4667, 0.6824), + rgb(0.9451, 0.7137, 0.8549), + rgb(0.9922, 0.8784, 0.9373), + rgb(0.9020, 0.9608, 0.8157), + rgb(0.7216, 0.8824, 0.5255), + rgb(0.4980, 0.7373, 0.2549), + rgb(0.3020, 0.5725, 0.1294) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7725, 0.1059, 0.4902), + rgb(0.8706, 0.4667, 0.6824), + rgb(0.9451, 0.7137, 0.8549), + rgb(0.9922, 0.8784, 0.9373), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.9020, 0.9608, 0.8157), + rgb(0.7216, 0.8824, 0.5255), + rgb(0.4980, 0.7373, 0.2549), + rgb(0.3020, 0.5725, 0.1294) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.5569, 0.0039, 0.3216), + rgb(0.7725, 0.1059, 0.4902), + rgb(0.8706, 0.4667, 0.6824), + rgb(0.9451, 0.7137, 0.8549), + rgb(0.9922, 0.8784, 0.9373), + rgb(0.9020, 0.9608, 0.8157), + rgb(0.7216, 0.8824, 0.5255), + rgb(0.4980, 0.7373, 0.2549), + rgb(0.3020, 0.5725, 0.1294), + rgb(0.1529, 0.3922, 0.0980) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.5569, 0.0039, 0.3216), + rgb(0.7725, 0.1059, 0.4902), + rgb(0.8706, 0.4667, 0.6824), + rgb(0.9451, 0.7137, 0.8549), + rgb(0.9922, 0.8784, 0.9373), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.9020, 0.9608, 0.8157), + rgb(0.7216, 0.8824, 0.5255), + rgb(0.4980, 0.7373, 0.2549), + rgb(0.3020, 0.5725, 0.1294), + rgb(0.1529, 0.3922, 0.0980) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6863, 0.5529, 0.7647), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.4980, 0.7490, 0.4824) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4824, 0.1961, 0.5804), + rgb(0.7608, 0.6471, 0.8118), + rgb(0.6510, 0.8588, 0.6275), + rgb(0.0000, 0.5333, 0.2157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4824, 0.1961, 0.5804), + rgb(0.7608, 0.6471, 0.8118), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.6510, 0.8588, 0.6275), + rgb(0.0000, 0.5333, 0.2157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4627, 0.1647, 0.5137), + rgb(0.6863, 0.5529, 0.7647), + rgb(0.9059, 0.8314, 0.9098), + rgb(0.8510, 0.9412, 0.8275), + rgb(0.4980, 0.7490, 0.4824), + rgb(0.1059, 0.4706, 0.2157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4627, 0.1647, 0.5137), + rgb(0.6863, 0.5529, 0.7647), + rgb(0.9059, 0.8314, 0.9098), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8510, 0.9412, 0.8275), + rgb(0.4980, 0.7490, 0.4824), + rgb(0.1059, 0.4706, 0.2157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4627, 0.1647, 0.5137), + rgb(0.6000, 0.4392, 0.6706), + rgb(0.7608, 0.6471, 0.8118), + rgb(0.9059, 0.8314, 0.9098), + rgb(0.8510, 0.9412, 0.8275), + rgb(0.6510, 0.8588, 0.6275), + rgb(0.3529, 0.6824, 0.3804), + rgb(0.1059, 0.4706, 0.2157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4627, 0.1647, 0.5137), + rgb(0.6000, 0.4392, 0.6706), + rgb(0.7608, 0.6471, 0.8118), + rgb(0.9059, 0.8314, 0.9098), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8510, 0.9412, 0.8275), + rgb(0.6510, 0.8588, 0.6275), + rgb(0.3529, 0.6824, 0.3804), + rgb(0.1059, 0.4706, 0.2157) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.2510, 0.0000, 0.2941), + rgb(0.4627, 0.1647, 0.5137), + rgb(0.6000, 0.4392, 0.6706), + rgb(0.7608, 0.6471, 0.8118), + rgb(0.9059, 0.8314, 0.9098), + rgb(0.8510, 0.9412, 0.8275), + rgb(0.6510, 0.8588, 0.6275), + rgb(0.3529, 0.6824, 0.3804), + rgb(0.1059, 0.4706, 0.2157), + rgb(0.0000, 0.2667, 0.1059) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.2510, 0.0000, 0.2941), + rgb(0.4627, 0.1647, 0.5137), + rgb(0.6000, 0.4392, 0.6706), + rgb(0.7608, 0.6471, 0.8118), + rgb(0.9059, 0.8314, 0.9098), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8510, 0.9412, 0.8275), + rgb(0.6510, 0.8588, 0.6275), + rgb(0.3529, 0.6824, 0.3804), + rgb(0.1059, 0.4706, 0.2157), + rgb(0.0000, 0.2667, 0.1059) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.9882, 0.5529, 0.3490), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.5686, 0.7490, 0.8588) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.0980, 0.1098), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.6706, 0.8510, 0.9137), + rgb(0.1725, 0.4824, 0.7137) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.0980, 0.1098), + rgb(0.9922, 0.6824, 0.3804), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.6706, 0.8510, 0.9137), + rgb(0.1725, 0.4824, 0.7137) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9961, 0.8784, 0.5647), + rgb(0.8784, 0.9529, 0.9725), + rgb(0.5686, 0.7490, 0.8588), + rgb(0.2706, 0.4588, 0.7059) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9961, 0.8784, 0.5647), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.8784, 0.9529, 0.9725), + rgb(0.5686, 0.7490, 0.8588), + rgb(0.2706, 0.4588, 0.7059) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5647), + rgb(0.8784, 0.9529, 0.9725), + rgb(0.6706, 0.8510, 0.9137), + rgb(0.4549, 0.6784, 0.8196), + rgb(0.2706, 0.4588, 0.7059) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5647), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.8784, 0.9529, 0.9725), + rgb(0.6706, 0.8510, 0.9137), + rgb(0.4549, 0.6784, 0.8196), + rgb(0.2706, 0.4588, 0.7059) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6471, 0.0000, 0.1490), + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5647), + rgb(0.8784, 0.9529, 0.9725), + rgb(0.6706, 0.8510, 0.9137), + rgb(0.4549, 0.6784, 0.8196), + rgb(0.2706, 0.4588, 0.7059), + rgb(0.1922, 0.2118, 0.5843) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6471, 0.0000, 0.1490), + rgb(0.8431, 0.1882, 0.1529), + rgb(0.9569, 0.4275, 0.2627), + rgb(0.9922, 0.6824, 0.3804), + rgb(0.9961, 0.8784, 0.5647), + rgb(1.0000, 1.0000, 0.7490), + rgb(0.8784, 0.9529, 0.9725), + rgb(0.6706, 0.8510, 0.9137), + rgb(0.4549, 0.6784, 0.8196), + rgb(0.2706, 0.4588, 0.7059), + rgb(0.1922, 0.2118, 0.5843) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.8471, 0.7020, 0.3961), + rgb(0.9608, 0.9608, 0.9608), + rgb(0.3529, 0.7059, 0.6745) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.3804, 0.1020), + rgb(0.8745, 0.7608, 0.4902), + rgb(0.5020, 0.8039, 0.7569), + rgb(0.0039, 0.5216, 0.4431) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.3804, 0.1020), + rgb(0.8745, 0.7608, 0.4902), + rgb(0.9608, 0.9608, 0.9608), + rgb(0.5020, 0.8039, 0.7569), + rgb(0.0039, 0.5216, 0.4431) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.5490, 0.3176, 0.0392), + rgb(0.8471, 0.7020, 0.3961), + rgb(0.9647, 0.9098, 0.7647), + rgb(0.7804, 0.9176, 0.8980), + rgb(0.3529, 0.7059, 0.6745), + rgb(0.0039, 0.4000, 0.3686) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.5490, 0.3176, 0.0392), + rgb(0.8471, 0.7020, 0.3961), + rgb(0.9647, 0.9098, 0.7647), + rgb(0.9608, 0.9608, 0.9608), + rgb(0.7804, 0.9176, 0.8980), + rgb(0.3529, 0.7059, 0.6745), + rgb(0.0039, 0.4000, 0.3686) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.5490, 0.3176, 0.0392), + rgb(0.7490, 0.5059, 0.1765), + rgb(0.8745, 0.7608, 0.4902), + rgb(0.9647, 0.9098, 0.7647), + rgb(0.7804, 0.9176, 0.8980), + rgb(0.5020, 0.8039, 0.7569), + rgb(0.2078, 0.5922, 0.5608), + rgb(0.0039, 0.4000, 0.3686) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.5490, 0.3176, 0.0392), + rgb(0.7490, 0.5059, 0.1765), + rgb(0.8745, 0.7608, 0.4902), + rgb(0.9647, 0.9098, 0.7647), + rgb(0.9608, 0.9608, 0.9608), + rgb(0.7804, 0.9176, 0.8980), + rgb(0.5020, 0.8039, 0.7569), + rgb(0.2078, 0.5922, 0.5608), + rgb(0.0039, 0.4000, 0.3686) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.3294, 0.1882, 0.0196), + rgb(0.5490, 0.3176, 0.0392), + rgb(0.7490, 0.5059, 0.1765), + rgb(0.8745, 0.7608, 0.4902), + rgb(0.9647, 0.9098, 0.7647), + rgb(0.7804, 0.9176, 0.8980), + rgb(0.5020, 0.8039, 0.7569), + rgb(0.2078, 0.5922, 0.5608), + rgb(0.0039, 0.4000, 0.3686), + rgb(0.0000, 0.2353, 0.1882) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.3294, 0.1882, 0.0196), + rgb(0.5490, 0.3176, 0.0392), + rgb(0.7490, 0.5059, 0.1765), + rgb(0.8745, 0.7608, 0.4902), + rgb(0.9647, 0.9098, 0.7647), + rgb(0.9608, 0.9608, 0.9608), + rgb(0.7804, 0.9176, 0.8980), + rgb(0.5020, 0.8039, 0.7569), + rgb(0.2078, 0.5922, 0.5608), + rgb(0.0039, 0.4000, 0.3686), + rgb(0.0000, 0.2353, 0.1882) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.9373, 0.5412, 0.3843), + rgb(1.0000, 1.0000, 1.0000), + rgb(0.6000, 0.6000, 0.6000) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7922, 0.0000, 0.1255), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.7294, 0.7294, 0.7294), + rgb(0.2510, 0.2510, 0.2510) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7922, 0.0000, 0.1255), + rgb(0.9569, 0.6471, 0.5098), + rgb(1.0000, 1.0000, 1.0000), + rgb(0.7294, 0.7294, 0.7294), + rgb(0.2510, 0.2510, 0.2510) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6980, 0.0941, 0.1686), + rgb(0.9373, 0.5412, 0.3843), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.8784, 0.8784, 0.8784), + rgb(0.6000, 0.6000, 0.6000), + rgb(0.3020, 0.3020, 0.3020) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6980, 0.0941, 0.1686), + rgb(0.9373, 0.5412, 0.3843), + rgb(0.9922, 0.8588, 0.7804), + rgb(1.0000, 1.0000, 1.0000), + rgb(0.8784, 0.8784, 0.8784), + rgb(0.6000, 0.6000, 0.6000), + rgb(0.3020, 0.3020, 0.3020) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6980, 0.0941, 0.1686), + rgb(0.8392, 0.3765, 0.3020), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.8784, 0.8784, 0.8784), + rgb(0.7294, 0.7294, 0.7294), + rgb(0.5294, 0.5294, 0.5294), + rgb(0.3020, 0.3020, 0.3020) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.6980, 0.0941, 0.1686), + rgb(0.8392, 0.3765, 0.3020), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9922, 0.8588, 0.7804), + rgb(1.0000, 1.0000, 1.0000), + rgb(0.8784, 0.8784, 0.8784), + rgb(0.7294, 0.7294, 0.7294), + rgb(0.5294, 0.5294, 0.5294), + rgb(0.3020, 0.3020, 0.3020) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4039, 0.0000, 0.1216), + rgb(0.6980, 0.0941, 0.1686), + rgb(0.8392, 0.3765, 0.3020), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9922, 0.8588, 0.7804), + rgb(0.8784, 0.8784, 0.8784), + rgb(0.7294, 0.7294, 0.7294), + rgb(0.5294, 0.5294, 0.5294), + rgb(0.3020, 0.3020, 0.3020), + rgb(0.1020, 0.1020, 0.1020) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4039, 0.0000, 0.1216), + rgb(0.6980, 0.0941, 0.1686), + rgb(0.8392, 0.3765, 0.3020), + rgb(0.9569, 0.6471, 0.5098), + rgb(0.9922, 0.8588, 0.7804), + rgb(1.0000, 1.0000, 1.0000), + rgb(0.8784, 0.8784, 0.8784), + rgb(0.7294, 0.7294, 0.7294), + rgb(0.5294, 0.5294, 0.5294), + rgb(0.3020, 0.3020, 0.3020), + rgb(0.1020, 0.1020, 0.1020) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.6392, 0.2510), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.6000, 0.5569, 0.7647) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.9020, 0.3804, 0.0039), + rgb(0.9922, 0.7216, 0.3882), + rgb(0.6980, 0.6706, 0.8235), + rgb(0.3686, 0.2353, 0.6000) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.9020, 0.3804, 0.0039), + rgb(0.9922, 0.7216, 0.3882), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.6980, 0.6706, 0.8235), + rgb(0.3686, 0.2353, 0.6000) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.3451, 0.0235), + rgb(0.9451, 0.6392, 0.2510), + rgb(0.9961, 0.8784, 0.7137), + rgb(0.8471, 0.8549, 0.9216), + rgb(0.6000, 0.5569, 0.7647), + rgb(0.3294, 0.1529, 0.5333) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.3451, 0.0235), + rgb(0.9451, 0.6392, 0.2510), + rgb(0.9961, 0.8784, 0.7137), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8471, 0.8549, 0.9216), + rgb(0.6000, 0.5569, 0.7647), + rgb(0.3294, 0.1529, 0.5333) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.3451, 0.0235), + rgb(0.8784, 0.5098, 0.0784), + rgb(0.9922, 0.7216, 0.3882), + rgb(0.9961, 0.8784, 0.7137), + rgb(0.8471, 0.8549, 0.9216), + rgb(0.6980, 0.6706, 0.8235), + rgb(0.5020, 0.4510, 0.6745), + rgb(0.3294, 0.1529, 0.5333) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.3451, 0.0235), + rgb(0.8784, 0.5098, 0.0784), + rgb(0.9922, 0.7216, 0.3882), + rgb(0.9961, 0.8784, 0.7137), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8471, 0.8549, 0.9216), + rgb(0.6980, 0.6706, 0.8235), + rgb(0.5020, 0.4510, 0.6745), + rgb(0.3294, 0.1529, 0.5333) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4980, 0.2314, 0.0314), + rgb(0.7020, 0.3451, 0.0235), + rgb(0.8784, 0.5098, 0.0784), + rgb(0.9922, 0.7216, 0.3882), + rgb(0.9961, 0.8784, 0.7137), + rgb(0.8471, 0.8549, 0.9216), + rgb(0.6980, 0.6706, 0.8235), + rgb(0.5020, 0.4510, 0.6745), + rgb(0.3294, 0.1529, 0.5333), + rgb(0.1765, 0.0000, 0.2941) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4980, 0.2314, 0.0314), + rgb(0.7020, 0.3451, 0.0235), + rgb(0.8784, 0.5098, 0.0784), + rgb(0.9922, 0.7216, 0.3882), + rgb(0.9961, 0.8784, 0.7137), + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8471, 0.8549, 0.9216), + rgb(0.6980, 0.6706, 0.8235), + rgb(0.5020, 0.4510, 0.6745), + rgb(0.3294, 0.1529, 0.5333), + rgb(0.1765, 0.0000, 0.2941) + ), ColorBrewer2Type.Diverging + ), + ColorBrewer2Palette( + listOf( + rgb(0.4000, 0.7608, 0.6471), + rgb(0.9882, 0.5529, 0.3843), + rgb(0.5529, 0.6275, 0.7961) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4000, 0.7608, 0.6471), + rgb(0.9882, 0.5529, 0.3843), + rgb(0.5529, 0.6275, 0.7961), + rgb(0.9059, 0.5412, 0.7647) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4000, 0.7608, 0.6471), + rgb(0.9882, 0.5529, 0.3843), + rgb(0.5529, 0.6275, 0.7961), + rgb(0.9059, 0.5412, 0.7647), + rgb(0.6510, 0.8471, 0.3294) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4000, 0.7608, 0.6471), + rgb(0.9882, 0.5529, 0.3843), + rgb(0.5529, 0.6275, 0.7961), + rgb(0.9059, 0.5412, 0.7647), + rgb(0.6510, 0.8471, 0.3294), + rgb(1.0000, 0.8510, 0.1843) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4000, 0.7608, 0.6471), + rgb(0.9882, 0.5529, 0.3843), + rgb(0.5529, 0.6275, 0.7961), + rgb(0.9059, 0.5412, 0.7647), + rgb(0.6510, 0.8471, 0.3294), + rgb(1.0000, 0.8510, 0.1843), + rgb(0.8980, 0.7686, 0.5804) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4000, 0.7608, 0.6471), + rgb(0.9882, 0.5529, 0.3843), + rgb(0.5529, 0.6275, 0.7961), + rgb(0.9059, 0.5412, 0.7647), + rgb(0.6510, 0.8471, 0.3294), + rgb(1.0000, 0.8510, 0.1843), + rgb(0.8980, 0.7686, 0.5804), + rgb(0.7020, 0.7020, 0.7020) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4980, 0.7882, 0.4980), + rgb(0.7451, 0.6824, 0.8314), + rgb(0.9922, 0.7529, 0.5255) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4980, 0.7882, 0.4980), + rgb(0.7451, 0.6824, 0.8314), + rgb(0.9922, 0.7529, 0.5255), + rgb(1.0000, 1.0000, 0.6000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4980, 0.7882, 0.4980), + rgb(0.7451, 0.6824, 0.8314), + rgb(0.9922, 0.7529, 0.5255), + rgb(1.0000, 1.0000, 0.6000), + rgb(0.2196, 0.4235, 0.6902) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4980, 0.7882, 0.4980), + rgb(0.7451, 0.6824, 0.8314), + rgb(0.9922, 0.7529, 0.5255), + rgb(1.0000, 1.0000, 0.6000), + rgb(0.2196, 0.4235, 0.6902), + rgb(0.9412, 0.0078, 0.4980) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4980, 0.7882, 0.4980), + rgb(0.7451, 0.6824, 0.8314), + rgb(0.9922, 0.7529, 0.5255), + rgb(1.0000, 1.0000, 0.6000), + rgb(0.2196, 0.4235, 0.6902), + rgb(0.9412, 0.0078, 0.4980), + rgb(0.7490, 0.3569, 0.0902) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.4980, 0.7882, 0.4980), + rgb(0.7451, 0.6824, 0.8314), + rgb(0.9922, 0.7529, 0.5255), + rgb(1.0000, 1.0000, 0.6000), + rgb(0.2196, 0.4235, 0.6902), + rgb(0.9412, 0.0078, 0.4980), + rgb(0.7490, 0.3569, 0.0902), + rgb(0.4000, 0.4000, 0.4000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.8941, 0.1020, 0.1098), + rgb(0.2157, 0.4941, 0.7216), + rgb(0.3020, 0.6863, 0.2902) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.8941, 0.1020, 0.1098), + rgb(0.2157, 0.4941, 0.7216), + rgb(0.3020, 0.6863, 0.2902), + rgb(0.5961, 0.3059, 0.6392) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.8941, 0.1020, 0.1098), + rgb(0.2157, 0.4941, 0.7216), + rgb(0.3020, 0.6863, 0.2902), + rgb(0.5961, 0.3059, 0.6392), + rgb(1.0000, 0.4980, 0.0000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.8941, 0.1020, 0.1098), + rgb(0.2157, 0.4941, 0.7216), + rgb(0.3020, 0.6863, 0.2902), + rgb(0.5961, 0.3059, 0.6392), + rgb(1.0000, 0.4980, 0.0000), + rgb(1.0000, 1.0000, 0.2000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.8941, 0.1020, 0.1098), + rgb(0.2157, 0.4941, 0.7216), + rgb(0.3020, 0.6863, 0.2902), + rgb(0.5961, 0.3059, 0.6392), + rgb(1.0000, 0.4980, 0.0000), + rgb(1.0000, 1.0000, 0.2000), + rgb(0.6510, 0.3373, 0.1569) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.8941, 0.1020, 0.1098), + rgb(0.2157, 0.4941, 0.7216), + rgb(0.3020, 0.6863, 0.2902), + rgb(0.5961, 0.3059, 0.6392), + rgb(1.0000, 0.4980, 0.0000), + rgb(1.0000, 1.0000, 0.2000), + rgb(0.6510, 0.3373, 0.1569), + rgb(0.9686, 0.5059, 0.7490) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.8941, 0.1020, 0.1098), + rgb(0.2157, 0.4941, 0.7216), + rgb(0.3020, 0.6863, 0.2902), + rgb(0.5961, 0.3059, 0.6392), + rgb(1.0000, 0.4980, 0.0000), + rgb(1.0000, 1.0000, 0.2000), + rgb(0.6510, 0.3373, 0.1569), + rgb(0.9686, 0.5059, 0.7490), + rgb(0.6000, 0.6000, 0.6000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471), + rgb(0.5020, 0.6941, 0.8275) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471), + rgb(0.5020, 0.6941, 0.8275), + rgb(0.9922, 0.7059, 0.3843) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471), + rgb(0.5020, 0.6941, 0.8275), + rgb(0.9922, 0.7059, 0.3843), + rgb(0.7020, 0.8706, 0.4118) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471), + rgb(0.5020, 0.6941, 0.8275), + rgb(0.9922, 0.7059, 0.3843), + rgb(0.7020, 0.8706, 0.4118), + rgb(0.9882, 0.8039, 0.8980) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471), + rgb(0.5020, 0.6941, 0.8275), + rgb(0.9922, 0.7059, 0.3843), + rgb(0.7020, 0.8706, 0.4118), + rgb(0.9882, 0.8039, 0.8980), + rgb(0.8510, 0.8510, 0.8510) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471), + rgb(0.5020, 0.6941, 0.8275), + rgb(0.9922, 0.7059, 0.3843), + rgb(0.7020, 0.8706, 0.4118), + rgb(0.9882, 0.8039, 0.8980), + rgb(0.8510, 0.8510, 0.8510), + rgb(0.7373, 0.5020, 0.7412) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471), + rgb(0.5020, 0.6941, 0.8275), + rgb(0.9922, 0.7059, 0.3843), + rgb(0.7020, 0.8706, 0.4118), + rgb(0.9882, 0.8039, 0.8980), + rgb(0.8510, 0.8510, 0.8510), + rgb(0.7373, 0.5020, 0.7412), + rgb(0.8000, 0.9216, 0.7725) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.5529, 0.8275, 0.7804), + rgb(1.0000, 1.0000, 0.7020), + rgb(0.7451, 0.7294, 0.8549), + rgb(0.9843, 0.5020, 0.4471), + rgb(0.5020, 0.6941, 0.8275), + rgb(0.9922, 0.7059, 0.3843), + rgb(0.7020, 0.8706, 0.4118), + rgb(0.9882, 0.8039, 0.8980), + rgb(0.8510, 0.8510, 0.8510), + rgb(0.7373, 0.5020, 0.7412), + rgb(0.8000, 0.9216, 0.7725), + rgb(1.0000, 0.9294, 0.4353) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.1059, 0.6196, 0.4667), + rgb(0.8510, 0.3725, 0.0078), + rgb(0.4588, 0.4392, 0.7020) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.1059, 0.6196, 0.4667), + rgb(0.8510, 0.3725, 0.0078), + rgb(0.4588, 0.4392, 0.7020), + rgb(0.9059, 0.1608, 0.5412) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.1059, 0.6196, 0.4667), + rgb(0.8510, 0.3725, 0.0078), + rgb(0.4588, 0.4392, 0.7020), + rgb(0.9059, 0.1608, 0.5412), + rgb(0.4000, 0.6510, 0.1176) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.1059, 0.6196, 0.4667), + rgb(0.8510, 0.3725, 0.0078), + rgb(0.4588, 0.4392, 0.7020), + rgb(0.9059, 0.1608, 0.5412), + rgb(0.4000, 0.6510, 0.1176), + rgb(0.9020, 0.6706, 0.0078) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.1059, 0.6196, 0.4667), + rgb(0.8510, 0.3725, 0.0078), + rgb(0.4588, 0.4392, 0.7020), + rgb(0.9059, 0.1608, 0.5412), + rgb(0.4000, 0.6510, 0.1176), + rgb(0.9020, 0.6706, 0.0078), + rgb(0.6510, 0.4627, 0.1137) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.1059, 0.6196, 0.4667), + rgb(0.8510, 0.3725, 0.0078), + rgb(0.4588, 0.4392, 0.7020), + rgb(0.9059, 0.1608, 0.5412), + rgb(0.4000, 0.6510, 0.1176), + rgb(0.9020, 0.6706, 0.0078), + rgb(0.6510, 0.4627, 0.1137), + rgb(0.4000, 0.4000, 0.4000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725), + rgb(0.9843, 0.6039, 0.6000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725), + rgb(0.9843, 0.6039, 0.6000), + rgb(0.8902, 0.1020, 0.1098) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725), + rgb(0.9843, 0.6039, 0.6000), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.9922, 0.7490, 0.4353) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725), + rgb(0.9843, 0.6039, 0.6000), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.9922, 0.7490, 0.4353), + rgb(1.0000, 0.4980, 0.0000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725), + rgb(0.9843, 0.6039, 0.6000), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.9922, 0.7490, 0.4353), + rgb(1.0000, 0.4980, 0.0000), + rgb(0.7922, 0.6980, 0.8392) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725), + rgb(0.9843, 0.6039, 0.6000), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.9922, 0.7490, 0.4353), + rgb(1.0000, 0.4980, 0.0000), + rgb(0.7922, 0.6980, 0.8392), + rgb(0.4157, 0.2392, 0.6039) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725), + rgb(0.9843, 0.6039, 0.6000), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.9922, 0.7490, 0.4353), + rgb(1.0000, 0.4980, 0.0000), + rgb(0.7922, 0.6980, 0.8392), + rgb(0.4157, 0.2392, 0.6039), + rgb(1.0000, 1.0000, 0.6000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.6510, 0.8078, 0.8902), + rgb(0.1216, 0.4706, 0.7059), + rgb(0.6980, 0.8745, 0.5412), + rgb(0.2000, 0.6275, 0.1725), + rgb(0.9843, 0.6039, 0.6000), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.9922, 0.7490, 0.4353), + rgb(1.0000, 0.4980, 0.0000), + rgb(0.7922, 0.6980, 0.8392), + rgb(0.4157, 0.2392, 0.6039), + rgb(1.0000, 1.0000, 0.6000), + rgb(0.6941, 0.3490, 0.1569) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.8863, 0.8039), + rgb(0.9922, 0.8039, 0.6745), + rgb(0.7961, 0.8353, 0.9098) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.8863, 0.8039), + rgb(0.9922, 0.8039, 0.6745), + rgb(0.7961, 0.8353, 0.9098), + rgb(0.9569, 0.7922, 0.8941) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.8863, 0.8039), + rgb(0.9922, 0.8039, 0.6745), + rgb(0.7961, 0.8353, 0.9098), + rgb(0.9569, 0.7922, 0.8941), + rgb(0.9020, 0.9608, 0.7882) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.8863, 0.8039), + rgb(0.9922, 0.8039, 0.6745), + rgb(0.7961, 0.8353, 0.9098), + rgb(0.9569, 0.7922, 0.8941), + rgb(0.9020, 0.9608, 0.7882), + rgb(1.0000, 0.9490, 0.6824) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.8863, 0.8039), + rgb(0.9922, 0.8039, 0.6745), + rgb(0.7961, 0.8353, 0.9098), + rgb(0.9569, 0.7922, 0.8941), + rgb(0.9020, 0.9608, 0.7882), + rgb(1.0000, 0.9490, 0.6824), + rgb(0.9451, 0.8863, 0.8000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.7020, 0.8863, 0.8039), + rgb(0.9922, 0.8039, 0.6745), + rgb(0.7961, 0.8353, 0.9098), + rgb(0.9569, 0.7922, 0.8941), + rgb(0.9020, 0.9608, 0.7882), + rgb(1.0000, 0.9490, 0.6824), + rgb(0.9451, 0.8863, 0.8000), + rgb(0.8000, 0.8000, 0.8000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.9843, 0.7059, 0.6824), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.8000, 0.9216, 0.7725) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.9843, 0.7059, 0.6824), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.8706, 0.7961, 0.8941) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.9843, 0.7059, 0.6824), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.8706, 0.7961, 0.8941), + rgb(0.9961, 0.8510, 0.6510) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.9843, 0.7059, 0.6824), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.8706, 0.7961, 0.8941), + rgb(0.9961, 0.8510, 0.6510), + rgb(1.0000, 1.0000, 0.8000) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.9843, 0.7059, 0.6824), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.8706, 0.7961, 0.8941), + rgb(0.9961, 0.8510, 0.6510), + rgb(1.0000, 1.0000, 0.8000), + rgb(0.8980, 0.8471, 0.7412) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.9843, 0.7059, 0.6824), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.8706, 0.7961, 0.8941), + rgb(0.9961, 0.8510, 0.6510), + rgb(1.0000, 1.0000, 0.8000), + rgb(0.8980, 0.8471, 0.7412), + rgb(0.9922, 0.8549, 0.9255) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.9843, 0.7059, 0.6824), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.8706, 0.7961, 0.8941), + rgb(0.9961, 0.8510, 0.6510), + rgb(1.0000, 1.0000, 0.8000), + rgb(0.8980, 0.8471, 0.7412), + rgb(0.9922, 0.8549, 0.9255), + rgb(0.9490, 0.9490, 0.9490) + ), ColorBrewer2Type.Qualitative + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9098, 0.7843), + rgb(0.9922, 0.7333, 0.5176), + rgb(0.8902, 0.2902, 0.2000) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9412, 0.8510), + rgb(0.9922, 0.8000, 0.5412), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.8431, 0.1882, 0.1216) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9412, 0.8510), + rgb(0.9922, 0.8000, 0.5412), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.8902, 0.2902, 0.2000), + rgb(0.7020, 0.0000, 0.0000) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9412, 0.8510), + rgb(0.9922, 0.8314, 0.6196), + rgb(0.9922, 0.7333, 0.5176), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.8902, 0.2902, 0.2000), + rgb(0.7020, 0.0000, 0.0000) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9412, 0.8510), + rgb(0.9922, 0.8314, 0.6196), + rgb(0.9922, 0.7333, 0.5176), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9373, 0.3961, 0.2824), + rgb(0.8431, 0.1882, 0.1216), + rgb(0.6000, 0.0000, 0.0000) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.9255), + rgb(0.9961, 0.9098, 0.7843), + rgb(0.9922, 0.8314, 0.6196), + rgb(0.9922, 0.7333, 0.5176), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9373, 0.3961, 0.2824), + rgb(0.8431, 0.1882, 0.1216), + rgb(0.6000, 0.0000, 0.0000) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.9255), + rgb(0.9961, 0.9098, 0.7843), + rgb(0.9922, 0.8314, 0.6196), + rgb(0.9922, 0.7333, 0.5176), + rgb(0.9882, 0.5529, 0.3490), + rgb(0.9373, 0.3961, 0.2824), + rgb(0.8431, 0.1882, 0.1216), + rgb(0.7020, 0.0000, 0.0000), + rgb(0.4980, 0.0000, 0.0000) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9255, 0.9059, 0.9490), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.1686, 0.5490, 0.7451) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.9333, 0.9647), + rgb(0.7412, 0.7882, 0.8824), + rgb(0.4549, 0.6627, 0.8118), + rgb(0.0196, 0.4392, 0.6902) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.9333, 0.9647), + rgb(0.7412, 0.7882, 0.8824), + rgb(0.4549, 0.6627, 0.8118), + rgb(0.1686, 0.5490, 0.7451), + rgb(0.0157, 0.3529, 0.5529) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.9333, 0.9647), + rgb(0.8157, 0.8196, 0.9020), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.4549, 0.6627, 0.8118), + rgb(0.1686, 0.5490, 0.7451), + rgb(0.0157, 0.3529, 0.5529) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.9333, 0.9647), + rgb(0.8157, 0.8196, 0.9020), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.4549, 0.6627, 0.8118), + rgb(0.2118, 0.5647, 0.7529), + rgb(0.0196, 0.4392, 0.6902), + rgb(0.0118, 0.3059, 0.4824) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.9843), + rgb(0.9255, 0.9059, 0.9490), + rgb(0.8157, 0.8196, 0.9020), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.4549, 0.6627, 0.8118), + rgb(0.2118, 0.5647, 0.7529), + rgb(0.0196, 0.4392, 0.6902), + rgb(0.0118, 0.3059, 0.4824) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.9843), + rgb(0.9255, 0.9059, 0.9490), + rgb(0.8157, 0.8196, 0.9020), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.4549, 0.6627, 0.8118), + rgb(0.2118, 0.5647, 0.7529), + rgb(0.0196, 0.4392, 0.6902), + rgb(0.0157, 0.3529, 0.5529), + rgb(0.0078, 0.2196, 0.3451) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.8784, 0.9255, 0.9569), + rgb(0.6196, 0.7373, 0.8549), + rgb(0.5333, 0.3373, 0.6549) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9843), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.5490, 0.5882, 0.7765), + rgb(0.5333, 0.2549, 0.6157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9843), + rgb(0.7020, 0.8039, 0.8902), + rgb(0.5490, 0.5882, 0.7765), + rgb(0.5333, 0.3373, 0.6549), + rgb(0.5059, 0.0588, 0.4863) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9843), + rgb(0.7490, 0.8275, 0.9020), + rgb(0.6196, 0.7373, 0.8549), + rgb(0.5490, 0.5882, 0.7765), + rgb(0.5333, 0.3373, 0.6549), + rgb(0.5059, 0.0588, 0.4863) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9843), + rgb(0.7490, 0.8275, 0.9020), + rgb(0.6196, 0.7373, 0.8549), + rgb(0.5490, 0.5882, 0.7765), + rgb(0.5490, 0.4196, 0.6941), + rgb(0.5333, 0.2549, 0.6157), + rgb(0.4314, 0.0039, 0.4196) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.9922), + rgb(0.8784, 0.9255, 0.9569), + rgb(0.7490, 0.8275, 0.9020), + rgb(0.6196, 0.7373, 0.8549), + rgb(0.5490, 0.5882, 0.7765), + rgb(0.5490, 0.4196, 0.6941), + rgb(0.5333, 0.2549, 0.6157), + rgb(0.4314, 0.0039, 0.4196) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.9922), + rgb(0.8784, 0.9255, 0.9569), + rgb(0.7490, 0.8275, 0.9020), + rgb(0.6196, 0.7373, 0.8549), + rgb(0.5490, 0.5882, 0.7765), + rgb(0.5490, 0.4196, 0.6941), + rgb(0.5333, 0.2549, 0.6157), + rgb(0.5059, 0.0588, 0.4863), + rgb(0.3020, 0.0000, 0.2941) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9020, 0.8078), + rgb(0.9922, 0.6824, 0.4196), + rgb(0.9020, 0.3333, 0.0510) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9294, 0.8706), + rgb(0.9922, 0.7451, 0.5216), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.8510, 0.2784, 0.0039) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9294, 0.8706), + rgb(0.9922, 0.7451, 0.5216), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9020, 0.3333, 0.0510), + rgb(0.6510, 0.2118, 0.0118) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9294, 0.8706), + rgb(0.9922, 0.8157, 0.6353), + rgb(0.9922, 0.6824, 0.4196), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9020, 0.3333, 0.0510), + rgb(0.6510, 0.2118, 0.0118) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9294, 0.8706), + rgb(0.9922, 0.8157, 0.6353), + rgb(0.9922, 0.6824, 0.4196), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9451, 0.4118, 0.0745), + rgb(0.8510, 0.2824, 0.0039), + rgb(0.5490, 0.1765, 0.0157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9608, 0.9216), + rgb(0.9961, 0.9020, 0.8078), + rgb(0.9922, 0.8157, 0.6353), + rgb(0.9922, 0.6824, 0.4196), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9451, 0.4118, 0.0745), + rgb(0.8510, 0.2824, 0.0039), + rgb(0.5490, 0.1765, 0.0157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9608, 0.9216), + rgb(0.9961, 0.9020, 0.8078), + rgb(0.9922, 0.8157, 0.6353), + rgb(0.9922, 0.6824, 0.4196), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9451, 0.4118, 0.0745), + rgb(0.8510, 0.2824, 0.0039), + rgb(0.6510, 0.2118, 0.0118), + rgb(0.4980, 0.1529, 0.0157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.8980, 0.9608, 0.9765), + rgb(0.6000, 0.8471, 0.7882), + rgb(0.1725, 0.6353, 0.3725) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9843), + rgb(0.6980, 0.8863, 0.8863), + rgb(0.4000, 0.7608, 0.6431), + rgb(0.1373, 0.5451, 0.2706) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9843), + rgb(0.6980, 0.8863, 0.8863), + rgb(0.4000, 0.7608, 0.6431), + rgb(0.1725, 0.6353, 0.3725), + rgb(0.0000, 0.4275, 0.1725) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9843), + rgb(0.8000, 0.9255, 0.9020), + rgb(0.6000, 0.8471, 0.7882), + rgb(0.4000, 0.7608, 0.6431), + rgb(0.1725, 0.6353, 0.3725), + rgb(0.0000, 0.4275, 0.1725) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9843), + rgb(0.8000, 0.9255, 0.9020), + rgb(0.6000, 0.8471, 0.7882), + rgb(0.4000, 0.7608, 0.6431), + rgb(0.2549, 0.6824, 0.4627), + rgb(0.1373, 0.5451, 0.2706), + rgb(0.0000, 0.3451, 0.1412) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.9922), + rgb(0.8980, 0.9608, 0.9765), + rgb(0.8000, 0.9255, 0.9020), + rgb(0.6000, 0.8471, 0.7882), + rgb(0.4000, 0.7608, 0.6431), + rgb(0.2549, 0.6824, 0.4627), + rgb(0.1373, 0.5451, 0.2706), + rgb(0.0000, 0.3451, 0.1412) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.9922), + rgb(0.8980, 0.9608, 0.9765), + rgb(0.8000, 0.9255, 0.9020), + rgb(0.6000, 0.8471, 0.7882), + rgb(0.4000, 0.7608, 0.6431), + rgb(0.2549, 0.6824, 0.4627), + rgb(0.1373, 0.5451, 0.2706), + rgb(0.0000, 0.4275, 0.1725), + rgb(0.0000, 0.2667, 0.1059) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.7373), + rgb(0.9961, 0.7686, 0.3098), + rgb(0.8510, 0.3725, 0.0549) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8314), + rgb(0.9961, 0.8510, 0.5569), + rgb(0.9961, 0.6000, 0.1608), + rgb(0.8000, 0.2980, 0.0078) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8314), + rgb(0.9961, 0.8510, 0.5569), + rgb(0.9961, 0.6000, 0.1608), + rgb(0.8510, 0.3725, 0.0549), + rgb(0.6000, 0.2039, 0.0157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8314), + rgb(0.9961, 0.8902, 0.5686), + rgb(0.9961, 0.7686, 0.3098), + rgb(0.9961, 0.6000, 0.1608), + rgb(0.8510, 0.3725, 0.0549), + rgb(0.6000, 0.2039, 0.0157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8314), + rgb(0.9961, 0.8902, 0.5686), + rgb(0.9961, 0.7686, 0.3098), + rgb(0.9961, 0.6000, 0.1608), + rgb(0.9255, 0.4392, 0.0784), + rgb(0.8000, 0.2980, 0.0078), + rgb(0.5490, 0.1765, 0.0157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8980), + rgb(1.0000, 0.9686, 0.7373), + rgb(0.9961, 0.8902, 0.5686), + rgb(0.9961, 0.7686, 0.3098), + rgb(0.9961, 0.6000, 0.1608), + rgb(0.9255, 0.4392, 0.0784), + rgb(0.8000, 0.2980, 0.0078), + rgb(0.5490, 0.1765, 0.0157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8980), + rgb(1.0000, 0.9686, 0.7373), + rgb(0.9961, 0.8902, 0.5686), + rgb(0.9961, 0.7686, 0.3098), + rgb(0.9961, 0.6000, 0.1608), + rgb(0.9255, 0.4392, 0.0784), + rgb(0.8000, 0.2980, 0.0078), + rgb(0.6000, 0.2039, 0.0157), + rgb(0.4000, 0.1451, 0.0235) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.7255), + rgb(0.6784, 0.8667, 0.5569), + rgb(0.1922, 0.6392, 0.3294) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(0.7608, 0.9020, 0.6000), + rgb(0.4706, 0.7765, 0.4745), + rgb(0.1373, 0.5176, 0.2627) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(0.7608, 0.9020, 0.6000), + rgb(0.4706, 0.7765, 0.4745), + rgb(0.1922, 0.6392, 0.3294), + rgb(0.0000, 0.4078, 0.2157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(0.8510, 0.9412, 0.6392), + rgb(0.6784, 0.8667, 0.5569), + rgb(0.4706, 0.7765, 0.4745), + rgb(0.1922, 0.6392, 0.3294), + rgb(0.0000, 0.4078, 0.2157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(0.8510, 0.9412, 0.6392), + rgb(0.6784, 0.8667, 0.5569), + rgb(0.4706, 0.7765, 0.4745), + rgb(0.2549, 0.6706, 0.3647), + rgb(0.1373, 0.5176, 0.2627), + rgb(0.0000, 0.3529, 0.1961) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8980), + rgb(0.9686, 0.9882, 0.7255), + rgb(0.8510, 0.9412, 0.6392), + rgb(0.6784, 0.8667, 0.5569), + rgb(0.4706, 0.7765, 0.4745), + rgb(0.2549, 0.6706, 0.3647), + rgb(0.1373, 0.5176, 0.2627), + rgb(0.0000, 0.3529, 0.1961) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8980), + rgb(0.9686, 0.9882, 0.7255), + rgb(0.8510, 0.9412, 0.6392), + rgb(0.6784, 0.8667, 0.5569), + rgb(0.4706, 0.7765, 0.4745), + rgb(0.2549, 0.6706, 0.3647), + rgb(0.1373, 0.5176, 0.2627), + rgb(0.0000, 0.4078, 0.2157), + rgb(0.0000, 0.2706, 0.1608) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.8784, 0.8235), + rgb(0.9882, 0.5725, 0.4471), + rgb(0.8706, 0.1765, 0.1490) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.8980, 0.8510), + rgb(0.9882, 0.6824, 0.5686), + rgb(0.9843, 0.4157, 0.2902), + rgb(0.7961, 0.0941, 0.1137) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.8980, 0.8510), + rgb(0.9882, 0.6824, 0.5686), + rgb(0.9843, 0.4157, 0.2902), + rgb(0.8706, 0.1765, 0.1490), + rgb(0.6471, 0.0588, 0.0824) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.8980, 0.8510), + rgb(0.9882, 0.7333, 0.6314), + rgb(0.9882, 0.5725, 0.4471), + rgb(0.9843, 0.4157, 0.2902), + rgb(0.8706, 0.1765, 0.1490), + rgb(0.6471, 0.0588, 0.0824) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.8980, 0.8510), + rgb(0.9882, 0.7333, 0.6314), + rgb(0.9882, 0.5725, 0.4471), + rgb(0.9843, 0.4157, 0.2902), + rgb(0.9373, 0.2314, 0.1725), + rgb(0.7961, 0.0941, 0.1137), + rgb(0.6000, 0.0000, 0.0510) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9608, 0.9412), + rgb(0.9961, 0.8784, 0.8235), + rgb(0.9882, 0.7333, 0.6314), + rgb(0.9882, 0.5725, 0.4471), + rgb(0.9843, 0.4157, 0.2902), + rgb(0.9373, 0.2314, 0.1725), + rgb(0.7961, 0.0941, 0.1137), + rgb(0.6000, 0.0000, 0.0510) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9608, 0.9412), + rgb(0.9961, 0.8784, 0.8235), + rgb(0.9882, 0.7333, 0.6314), + rgb(0.9882, 0.5725, 0.4471), + rgb(0.9843, 0.4157, 0.2902), + rgb(0.9373, 0.2314, 0.1725), + rgb(0.7961, 0.0941, 0.1137), + rgb(0.6471, 0.0588, 0.0824), + rgb(0.4039, 0.0000, 0.0510) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9922, 0.8784, 0.8667), + rgb(0.9804, 0.6235, 0.7098), + rgb(0.7725, 0.1059, 0.5412) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9216, 0.8863), + rgb(0.9843, 0.7059, 0.7255), + rgb(0.9686, 0.4078, 0.6314), + rgb(0.6824, 0.0039, 0.4941) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9216, 0.8863), + rgb(0.9843, 0.7059, 0.7255), + rgb(0.9686, 0.4078, 0.6314), + rgb(0.7725, 0.1059, 0.5412), + rgb(0.4784, 0.0039, 0.4667) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9216, 0.8863), + rgb(0.9882, 0.7725, 0.7529), + rgb(0.9804, 0.6235, 0.7098), + rgb(0.9686, 0.4078, 0.6314), + rgb(0.7725, 0.1059, 0.5412), + rgb(0.4784, 0.0039, 0.4667) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9961, 0.9216, 0.8863), + rgb(0.9882, 0.7725, 0.7529), + rgb(0.9804, 0.6235, 0.7098), + rgb(0.9686, 0.4078, 0.6314), + rgb(0.8667, 0.2039, 0.5922), + rgb(0.6824, 0.0039, 0.4941), + rgb(0.4784, 0.0039, 0.4667) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.9529), + rgb(0.9922, 0.8784, 0.8667), + rgb(0.9882, 0.7725, 0.7529), + rgb(0.9804, 0.6235, 0.7098), + rgb(0.9686, 0.4078, 0.6314), + rgb(0.8667, 0.2039, 0.5922), + rgb(0.6824, 0.0039, 0.4941), + rgb(0.4784, 0.0039, 0.4667) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.9529), + rgb(0.9922, 0.8784, 0.8667), + rgb(0.9882, 0.7725, 0.7529), + rgb(0.9804, 0.6235, 0.7098), + rgb(0.9686, 0.4078, 0.6314), + rgb(0.8667, 0.2039, 0.5922), + rgb(0.6824, 0.0039, 0.4941), + rgb(0.4784, 0.0039, 0.4667), + rgb(0.2863, 0.0000, 0.4157) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.8980, 0.9608, 0.8784), + rgb(0.6314, 0.8510, 0.6078), + rgb(0.1922, 0.6392, 0.3294) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9137), + rgb(0.7294, 0.8941, 0.7020), + rgb(0.4549, 0.7686, 0.4627), + rgb(0.1373, 0.5451, 0.2706) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9137), + rgb(0.7294, 0.8941, 0.7020), + rgb(0.4549, 0.7686, 0.4627), + rgb(0.1922, 0.6392, 0.3294), + rgb(0.0000, 0.4275, 0.1725) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9137), + rgb(0.7804, 0.9137, 0.7529), + rgb(0.6314, 0.8510, 0.6078), + rgb(0.4549, 0.7686, 0.4627), + rgb(0.1922, 0.6392, 0.3294), + rgb(0.0000, 0.4275, 0.1725) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.9137), + rgb(0.7804, 0.9137, 0.7529), + rgb(0.6314, 0.8510, 0.6078), + rgb(0.4549, 0.7686, 0.4627), + rgb(0.2549, 0.6706, 0.3647), + rgb(0.1373, 0.5451, 0.2706), + rgb(0.0000, 0.3529, 0.1961) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.9608), + rgb(0.8980, 0.9608, 0.8784), + rgb(0.7804, 0.9137, 0.7529), + rgb(0.6314, 0.8510, 0.6078), + rgb(0.4549, 0.7686, 0.4627), + rgb(0.2549, 0.6706, 0.3647), + rgb(0.1373, 0.5451, 0.2706), + rgb(0.0000, 0.3529, 0.1961) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.9608), + rgb(0.8980, 0.9608, 0.8784), + rgb(0.7804, 0.9137, 0.7529), + rgb(0.6314, 0.8510, 0.6078), + rgb(0.4549, 0.7686, 0.4627), + rgb(0.2549, 0.6706, 0.3647), + rgb(0.1373, 0.5451, 0.2706), + rgb(0.0000, 0.4275, 0.1725), + rgb(0.0000, 0.2667, 0.1059) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9294, 0.9725, 0.6941), + rgb(0.4980, 0.8039, 0.7333), + rgb(0.1725, 0.4980, 0.7216) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(0.6314, 0.8549, 0.7059), + rgb(0.2549, 0.7137, 0.7686), + rgb(0.1333, 0.3686, 0.6588) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(0.6314, 0.8549, 0.7059), + rgb(0.2549, 0.7137, 0.7686), + rgb(0.1725, 0.4980, 0.7216), + rgb(0.1451, 0.2039, 0.5804) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(0.7804, 0.9137, 0.7059), + rgb(0.4980, 0.8039, 0.7333), + rgb(0.2549, 0.7137, 0.7686), + rgb(0.1725, 0.4980, 0.7216), + rgb(0.1451, 0.2039, 0.5804) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(0.7804, 0.9137, 0.7059), + rgb(0.4980, 0.8039, 0.7333), + rgb(0.2549, 0.7137, 0.7686), + rgb(0.1137, 0.5686, 0.7529), + rgb(0.1333, 0.3686, 0.6588), + rgb(0.0471, 0.1725, 0.5176) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8510), + rgb(0.9294, 0.9725, 0.6941), + rgb(0.7804, 0.9137, 0.7059), + rgb(0.4980, 0.8039, 0.7333), + rgb(0.2549, 0.7137, 0.7686), + rgb(0.1137, 0.5686, 0.7529), + rgb(0.1333, 0.3686, 0.6588), + rgb(0.0471, 0.1725, 0.5176) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8510), + rgb(0.9294, 0.9725, 0.6941), + rgb(0.7804, 0.9137, 0.7059), + rgb(0.4980, 0.8039, 0.7333), + rgb(0.2549, 0.7137, 0.7686), + rgb(0.1137, 0.5686, 0.7529), + rgb(0.1333, 0.3686, 0.6588), + rgb(0.1451, 0.2039, 0.5804), + rgb(0.0314, 0.1137, 0.3451) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9373, 0.9294, 0.9608), + rgb(0.7373, 0.7412, 0.8627), + rgb(0.4588, 0.4196, 0.6941) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9490, 0.9412, 0.9686), + rgb(0.7961, 0.7882, 0.8863), + rgb(0.6196, 0.6039, 0.7843), + rgb(0.4157, 0.3176, 0.6392) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9490, 0.9412, 0.9686), + rgb(0.7961, 0.7882, 0.8863), + rgb(0.6196, 0.6039, 0.7843), + rgb(0.4588, 0.4196, 0.6941), + rgb(0.3294, 0.1529, 0.5608) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9490, 0.9412, 0.9686), + rgb(0.8549, 0.8549, 0.9216), + rgb(0.7373, 0.7412, 0.8627), + rgb(0.6196, 0.6039, 0.7843), + rgb(0.4588, 0.4196, 0.6941), + rgb(0.3294, 0.1529, 0.5608) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9490, 0.9412, 0.9686), + rgb(0.8549, 0.8549, 0.9216), + rgb(0.7373, 0.7412, 0.8627), + rgb(0.6196, 0.6039, 0.7843), + rgb(0.5020, 0.4902, 0.7294), + rgb(0.4157, 0.3176, 0.6392), + rgb(0.2902, 0.0784, 0.5255) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9882, 0.9843, 0.9922), + rgb(0.9373, 0.9294, 0.9608), + rgb(0.8549, 0.8549, 0.9216), + rgb(0.7373, 0.7412, 0.8627), + rgb(0.6196, 0.6039, 0.7843), + rgb(0.5020, 0.4902, 0.7294), + rgb(0.4157, 0.3176, 0.6392), + rgb(0.2902, 0.0784, 0.5255) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9882, 0.9843, 0.9922), + rgb(0.9373, 0.9294, 0.9608), + rgb(0.8549, 0.8549, 0.9216), + rgb(0.7373, 0.7412, 0.8627), + rgb(0.6196, 0.6039, 0.7843), + rgb(0.5020, 0.4902, 0.7294), + rgb(0.4157, 0.3176, 0.6392), + rgb(0.3294, 0.1529, 0.5608), + rgb(0.2471, 0.0000, 0.4902) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.8784, 0.9529, 0.8588), + rgb(0.6588, 0.8667, 0.7098), + rgb(0.2627, 0.6353, 0.7922) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9412, 0.9765, 0.9098), + rgb(0.7294, 0.8941, 0.7373), + rgb(0.4824, 0.8000, 0.7686), + rgb(0.1686, 0.5490, 0.7451) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9412, 0.9765, 0.9098), + rgb(0.7294, 0.8941, 0.7373), + rgb(0.4824, 0.8000, 0.7686), + rgb(0.2627, 0.6353, 0.7922), + rgb(0.0314, 0.4078, 0.6745) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9412, 0.9765, 0.9098), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.6588, 0.8667, 0.7098), + rgb(0.4824, 0.8000, 0.7686), + rgb(0.2627, 0.6353, 0.7922), + rgb(0.0314, 0.4078, 0.6745) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9412, 0.9765, 0.9098), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.6588, 0.8667, 0.7098), + rgb(0.4824, 0.8000, 0.7686), + rgb(0.3059, 0.7020, 0.8275), + rgb(0.1686, 0.5490, 0.7451), + rgb(0.0314, 0.3451, 0.6196) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.9412), + rgb(0.8784, 0.9529, 0.8588), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.6588, 0.8667, 0.7098), + rgb(0.4824, 0.8000, 0.7686), + rgb(0.3059, 0.7020, 0.8275), + rgb(0.1686, 0.5490, 0.7451), + rgb(0.0314, 0.3451, 0.6196) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9882, 0.9412), + rgb(0.8784, 0.9529, 0.8588), + rgb(0.8000, 0.9216, 0.7725), + rgb(0.6588, 0.8667, 0.7098), + rgb(0.4824, 0.8000, 0.7686), + rgb(0.3059, 0.7020, 0.8275), + rgb(0.1686, 0.5490, 0.7451), + rgb(0.0314, 0.4078, 0.6745), + rgb(0.0314, 0.2510, 0.5059) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9412, 0.9412, 0.9412), + rgb(0.7412, 0.7412, 0.7412), + rgb(0.3882, 0.3882, 0.3882) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8000, 0.8000, 0.8000), + rgb(0.5882, 0.5882, 0.5882), + rgb(0.3216, 0.3216, 0.3216) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8000, 0.8000, 0.8000), + rgb(0.5882, 0.5882, 0.5882), + rgb(0.3882, 0.3882, 0.3882), + rgb(0.1451, 0.1451, 0.1451) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8510, 0.8510, 0.8510), + rgb(0.7412, 0.7412, 0.7412), + rgb(0.5882, 0.5882, 0.5882), + rgb(0.3882, 0.3882, 0.3882), + rgb(0.1451, 0.1451, 0.1451) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9686, 0.9686), + rgb(0.8510, 0.8510, 0.8510), + rgb(0.7412, 0.7412, 0.7412), + rgb(0.5882, 0.5882, 0.5882), + rgb(0.4510, 0.4510, 0.4510), + rgb(0.3216, 0.3216, 0.3216), + rgb(0.1451, 0.1451, 0.1451) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 1.0000), + rgb(0.9412, 0.9412, 0.9412), + rgb(0.8510, 0.8510, 0.8510), + rgb(0.7412, 0.7412, 0.7412), + rgb(0.5882, 0.5882, 0.5882), + rgb(0.4510, 0.4510, 0.4510), + rgb(0.3216, 0.3216, 0.3216), + rgb(0.1451, 0.1451, 0.1451) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 1.0000), + rgb(0.9412, 0.9412, 0.9412), + rgb(0.8510, 0.8510, 0.8510), + rgb(0.7412, 0.7412, 0.7412), + rgb(0.5882, 0.5882, 0.5882), + rgb(0.4510, 0.4510, 0.4510), + rgb(0.3216, 0.3216, 0.3216), + rgb(0.1451, 0.1451, 0.1451), + rgb(0.0000, 0.0000, 0.0000) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9294, 0.6275), + rgb(0.9961, 0.6980, 0.2980), + rgb(0.9412, 0.2314, 0.1255) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.6980), + rgb(0.9961, 0.8000, 0.3608), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.8902, 0.1020, 0.1098) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.6980), + rgb(0.9961, 0.8000, 0.3608), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9412, 0.2314, 0.1255), + rgb(0.7412, 0.0000, 0.1490) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.6980), + rgb(0.9961, 0.8510, 0.4627), + rgb(0.9961, 0.6980, 0.2980), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9412, 0.2314, 0.1255), + rgb(0.7412, 0.0000, 0.1490) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.6980), + rgb(0.9961, 0.8510, 0.4627), + rgb(0.9961, 0.6980, 0.2980), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9882, 0.3059, 0.1647), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.6941, 0.0000, 0.1490) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(1.0000, 0.9294, 0.6275), + rgb(0.9961, 0.8510, 0.4627), + rgb(0.9961, 0.6980, 0.2980), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9882, 0.3059, 0.1647), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.6941, 0.0000, 0.1490) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 1.0000, 0.8000), + rgb(1.0000, 0.9294, 0.6275), + rgb(0.9961, 0.8510, 0.4627), + rgb(0.9961, 0.6980, 0.2980), + rgb(0.9922, 0.5529, 0.2353), + rgb(0.9882, 0.3059, 0.1647), + rgb(0.8902, 0.1020, 0.1098), + rgb(0.7412, 0.0000, 0.1490), + rgb(0.5020, 0.0000, 0.1490) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9059, 0.8824, 0.9373), + rgb(0.7882, 0.5804, 0.7804), + rgb(0.8667, 0.1098, 0.4667) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.9333, 0.9647), + rgb(0.8431, 0.7098, 0.8471), + rgb(0.8745, 0.3961, 0.6902), + rgb(0.8078, 0.0706, 0.3373) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.9333, 0.9647), + rgb(0.8431, 0.7098, 0.8471), + rgb(0.8745, 0.3961, 0.6902), + rgb(0.8667, 0.1098, 0.4667), + rgb(0.5961, 0.0000, 0.2627) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.9333, 0.9647), + rgb(0.8314, 0.7255, 0.8549), + rgb(0.7882, 0.5804, 0.7804), + rgb(0.8745, 0.3961, 0.6902), + rgb(0.8667, 0.1098, 0.4667), + rgb(0.5961, 0.0000, 0.2627) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9451, 0.9333, 0.9647), + rgb(0.8314, 0.7255, 0.8549), + rgb(0.7882, 0.5804, 0.7804), + rgb(0.8745, 0.3961, 0.6902), + rgb(0.9059, 0.1608, 0.5412), + rgb(0.8078, 0.0706, 0.3373), + rgb(0.5686, 0.0000, 0.2471) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9569, 0.9765), + rgb(0.9059, 0.8824, 0.9373), + rgb(0.8314, 0.7255, 0.8549), + rgb(0.7882, 0.5804, 0.7804), + rgb(0.8745, 0.3961, 0.6902), + rgb(0.9059, 0.1608, 0.5412), + rgb(0.8078, 0.0706, 0.3373), + rgb(0.5686, 0.0000, 0.2471) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9569, 0.9765), + rgb(0.9059, 0.8824, 0.9373), + rgb(0.8314, 0.7255, 0.8549), + rgb(0.7882, 0.5804, 0.7804), + rgb(0.8745, 0.3961, 0.6902), + rgb(0.9059, 0.1608, 0.5412), + rgb(0.8078, 0.0706, 0.3373), + rgb(0.5961, 0.0000, 0.2627), + rgb(0.4039, 0.0000, 0.1216) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.8706, 0.9216, 0.9686), + rgb(0.6196, 0.7922, 0.8824), + rgb(0.1922, 0.5098, 0.7412) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9373, 0.9529, 1.0000), + rgb(0.7412, 0.8431, 0.9059), + rgb(0.4196, 0.6824, 0.8392), + rgb(0.1294, 0.4431, 0.7098) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9373, 0.9529, 1.0000), + rgb(0.7412, 0.8431, 0.9059), + rgb(0.4196, 0.6824, 0.8392), + rgb(0.1922, 0.5098, 0.7412), + rgb(0.0314, 0.3176, 0.6118) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9373, 0.9529, 1.0000), + rgb(0.7765, 0.8588, 0.9373), + rgb(0.6196, 0.7922, 0.8824), + rgb(0.4196, 0.6824, 0.8392), + rgb(0.1922, 0.5098, 0.7412), + rgb(0.0314, 0.3176, 0.6118) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9373, 0.9529, 1.0000), + rgb(0.7765, 0.8588, 0.9373), + rgb(0.6196, 0.7922, 0.8824), + rgb(0.4196, 0.6824, 0.8392), + rgb(0.2588, 0.5725, 0.7765), + rgb(0.1294, 0.4431, 0.7098), + rgb(0.0314, 0.2706, 0.5804) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9843, 1.0000), + rgb(0.8706, 0.9216, 0.9686), + rgb(0.7765, 0.8588, 0.9373), + rgb(0.6196, 0.7922, 0.8824), + rgb(0.4196, 0.6824, 0.8392), + rgb(0.2588, 0.5725, 0.7765), + rgb(0.1294, 0.4431, 0.7098), + rgb(0.0314, 0.2706, 0.5804) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9686, 0.9843, 1.0000), + rgb(0.8706, 0.9216, 0.9686), + rgb(0.7765, 0.8588, 0.9373), + rgb(0.6196, 0.7922, 0.8824), + rgb(0.4196, 0.6824, 0.8392), + rgb(0.2588, 0.5725, 0.7765), + rgb(0.1294, 0.4431, 0.7098), + rgb(0.0314, 0.3176, 0.6118), + rgb(0.0314, 0.1882, 0.4196) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9255, 0.8863, 0.9412), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.1098, 0.5647, 0.6000) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9647, 0.9373, 0.9686), + rgb(0.7412, 0.7882, 0.8824), + rgb(0.4039, 0.6627, 0.8118), + rgb(0.0078, 0.5059, 0.5412) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9647, 0.9373, 0.9686), + rgb(0.7412, 0.7882, 0.8824), + rgb(0.4039, 0.6627, 0.8118), + rgb(0.1098, 0.5647, 0.6000), + rgb(0.0039, 0.4235, 0.3490) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9647, 0.9373, 0.9686), + rgb(0.8157, 0.8196, 0.9020), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.4039, 0.6627, 0.8118), + rgb(0.1098, 0.5647, 0.6000), + rgb(0.0039, 0.4235, 0.3490) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(0.9647, 0.9373, 0.9686), + rgb(0.8157, 0.8196, 0.9020), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.4039, 0.6627, 0.8118), + rgb(0.2118, 0.5647, 0.7529), + rgb(0.0078, 0.5059, 0.5412), + rgb(0.0039, 0.3922, 0.3137) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.9843), + rgb(0.9255, 0.8863, 0.9412), + rgb(0.8157, 0.8196, 0.9020), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.4039, 0.6627, 0.8118), + rgb(0.2118, 0.5647, 0.7529), + rgb(0.0078, 0.5059, 0.5412), + rgb(0.0039, 0.3922, 0.3137) + ), ColorBrewer2Type.Sequential + ), + ColorBrewer2Palette( + listOf( + rgb(1.0000, 0.9686, 0.9843), + rgb(0.9255, 0.8863, 0.9412), + rgb(0.8157, 0.8196, 0.9020), + rgb(0.6510, 0.7412, 0.8588), + rgb(0.4039, 0.6627, 0.8118), + rgb(0.2118, 0.5647, 0.7529), + rgb(0.0078, 0.5059, 0.5412), + rgb(0.0039, 0.4235, 0.3490), + rgb(0.0039, 0.2745, 0.2118) + ), ColorBrewer2Type.Sequential + ) +) + +fun colorBrewer2Palettes( + numberOfColors: Int? = null, + paletteType: ColorBrewer2Type = ColorBrewer2Type.Any +) = when { + numberOfColors == null && paletteType == ColorBrewer2Type.Any -> colorBrewer2 + paletteType == ColorBrewer2Type.Any -> colorBrewer2.filter { it.colors.size == numberOfColors } + else -> colorBrewer2.filter { it.type == paletteType } +} diff --git a/android/src/main/java/com/icegps/geotools/ktx/ColorRGBa.kt b/android/src/main/java/com/icegps/geotools/ktx/ColorRGBa.kt new file mode 100644 index 0000000..a806bd0 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/ktx/ColorRGBa.kt @@ -0,0 +1,23 @@ +package com.icegps.geotools.ktx + +import com.icegps.geotools.color.ColorRGBa + +/** + * @author tabidachinokaze + * @date 2025/11/25 + */ +fun ColorRGBa.toColorInt(): Int { + val clampedR = r.coerceIn(0.0, 1.0) + val clampedG = g.coerceIn(0.0, 1.0) + val clampedB = b.coerceIn(0.0, 1.0) + val clampedAlpha = alpha.coerceIn(0.0, 1.0) + + return ((clampedAlpha * 255).toInt() shl 24) or + ((clampedR * 255).toInt() shl 16) or + ((clampedG * 255).toInt() shl 8) or + ((clampedB * 255).toInt()) +} + +fun ColorRGBa.toColorHex(): String { + return String.format("#%06X", 0xFFFFFF and toColorInt()) +} diff --git a/android/src/main/java/com/icegps/geotools/ktx/Context.kt b/android/src/main/java/com/icegps/geotools/ktx/Context.kt new file mode 100644 index 0000000..14a919d --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/ktx/Context.kt @@ -0,0 +1,12 @@ +package com.icegps.geotools.ktx + +import android.content.Context +import android.widget.Toast + +/** + * @author tabidachinokaze + * @date 2025/11/25 + */ +fun Context.toast(text: String, duration: Int = Toast.LENGTH_SHORT) { + Toast.makeText(this, text, duration).show() +} \ No newline at end of file diff --git a/android/src/main/java/com/icegps/geotools/ktx/Vector2D.kt b/android/src/main/java/com/icegps/geotools/ktx/Vector2D.kt new file mode 100644 index 0000000..d929990 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/ktx/Vector2D.kt @@ -0,0 +1,24 @@ +package com.icegps.geotools.ktx + +import com.icegps.common.helper.GeoHelper +import com.icegps.math.geometry.Vector2D +import com.mapbox.geojson.Point + +/** + * @author tabidachinokaze + * @date 2025/11/26 + */ +fun Vector2D.toMapboxPoint(): Point { + val geoHelper = GeoHelper.getSharedInstance() + val wgs84 = geoHelper.enuToWGS84Object(GeoHelper.ENU(x = x, y = y)) + return Point.fromLngLat(wgs84.lon, wgs84.lat) +} + +/** + * Interpolates between the current vector and the given vector `o` by the specified mixing factor. + * + * @param o The target vector to interpolate towards. + * @param mix A mixing factor between 0 and 1 where `0` results in the current vector and `1` results in the vector `o`. + * @return A new vector that is the result of the interpolation. + */ +fun Vector2D.mix(o: Vector2D, mix: Double): Vector2D = this * (1 - mix) + o * mix diff --git a/android/src/main/java/com/icegps/geotools/ktx/Vector3D.kt b/android/src/main/java/com/icegps/geotools/ktx/Vector3D.kt new file mode 100644 index 0000000..235f595 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/ktx/Vector3D.kt @@ -0,0 +1,32 @@ +package com.icegps.geotools.ktx + +import com.icegps.common.helper.GeoHelper +import com.icegps.math.geometry.Rectangle +import com.icegps.math.geometry.Vector3D +import com.mapbox.geojson.Point + +fun Vector3D.niceStr(): String { + return "[$x, $y, $z]".format(this) +} + +fun List.niceStr(): String { + return joinToString(", ", "[", "]") { + it.niceStr() + } +} + +fun Vector3D.toMapboxPoint(): Point { + val geoHelper = GeoHelper.getSharedInstance() + return geoHelper.enuToWGS84Object(GeoHelper.ENU(x, y, z)).run { + Point.fromLngLat(lon, lat, hgt) + } +} + +val List.area: Rectangle + get() { + val minX = minOf { it.x } + val maxX = maxOf { it.x } + val minY = minOf { it.y } + val maxY = maxOf { it.y } + return Rectangle(x = minX, y = minY, width = maxX - minX, height = maxY - minY) + } diff --git a/android/src/main/java/com/icegps/geotools/marchingsquares/MarchingSquares.kt b/android/src/main/java/com/icegps/geotools/marchingsquares/MarchingSquares.kt new file mode 100644 index 0000000..a134003 --- /dev/null +++ b/android/src/main/java/com/icegps/geotools/marchingsquares/MarchingSquares.kt @@ -0,0 +1,219 @@ +package com.icegps.geotools.marchingsquares + +import com.icegps.math.geometry.Rectangle +import com.icegps.math.geometry.Vector2D +import com.icegps.math.geometry.Vector2I +import com.icegps.geotools.ktx.mix +import kotlin.math.max +import kotlin.math.min + +private const val closeEpsilon = 1E-6 + +data class Segment2D( + val start: Vector2D, + val control: List, + val end: Vector2D, + val corner: Boolean = false +) + +fun Segment2D(start: Vector2D, end: Vector2D, corner: Boolean = true) = + Segment2D(start, emptyList(), end, corner) + +fun Segment2D(start: Vector2D, c0: Vector2D, c1: Vector2D, end: Vector2D, corner: Boolean = true) = + Segment2D(start, listOf(c0, c1), end, corner) + +data class ShapeContour( + val segments: List, + val closed: Boolean, +) { + companion object { + val EMPTY = ShapeContour( + segments = emptyList(), + closed = false, + ) + + /** + * Creates a ShapeContour from a list of points, specifying whether the contour is closed and its y-axis polarity. + * + * @param points A list of points (Vector2) defining the vertices of the contour. + * @param closed Boolean indicating whether the contour should be closed (forms a loop). + * @return A ShapeContour object representing the resulting contour. + */ + fun fromPoints( + points: List, + closed: Boolean, + ): ShapeContour = if (points.isEmpty()) { + EMPTY + } else { + if (!closed) { + ShapeContour((0 until points.size - 1).map { + Segment2D( + points[it], + points[it + 1] + ) + }, false) + } else { + val d = (points.last() - points.first()).lengthSquared + val usePoints = if (d > closeEpsilon) points else points.dropLast(1) + ShapeContour((usePoints.indices).map { + Segment2D( + usePoints[it], + usePoints[(it + 1) % usePoints.size] + ) + }, true) + } + } + } +} + +data class LineSegment(val start: Vector2D, val end: Vector2D) + +/** + * Find contours for a function [f] using the marching squares algorithm. A contour is found when f(x) crosses zero. + * @param f the function + * @param area a rectangular area in which the function should be evaluated + * @param cellSize the size of the cells, smaller size gives higher resolution + * @param useInterpolation intersection points will be interpolated if true, default true + * @return a list of [ShapeContour] instances + */ +fun findContours( + f: (Vector2D) -> Double, + area: Rectangle, + cellSize: Double, + useInterpolation: Boolean = true +): List { + val segments = mutableListOf() + val values = mutableMapOf() + val segmentsMap = mutableMapOf>() + + for (y in 0 until (area.height / cellSize).toInt()) { + for (x in 0 until (area.width / cellSize).toInt()) { + values[Vector2I(x, y)] = f(Vector2D(x * cellSize + area.x, y * cellSize + area.y)) + } + } + + val zero = 0.0 + for (y in 0 until (area.height / cellSize).toInt()) { + for (x in 0 until (area.width / cellSize).toInt()) { + + // Here we check if we are at a right or top border. This is to ensure we create closed contours + // later on in the process. + val v00 = if (x == 0 || y == 0) zero else (values[Vector2I(x, y)] ?: zero) + val v10 = if (y == 0) zero else (values[Vector2I(x + 1, y)] ?: zero) + val v01 = if (x == 0) zero else (values[Vector2I(x, y + 1)] ?: zero) + val v11 = (values[Vector2I(x + 1, y + 1)] ?: zero) + + val p00 = Vector2D(x.toDouble(), y.toDouble()) * cellSize + area.topLeft + val p10 = Vector2D((x + 1).toDouble(), y.toDouble()) * cellSize + area.topLeft + val p01 = Vector2D(x.toDouble(), (y + 1).toDouble()) * cellSize + area.topLeft + val p11 = Vector2D((x + 1).toDouble(), (y + 1).toDouble()) * cellSize + area.topLeft + + val index = (if (v00 >= 0.0) 1 else 0) + + (if (v10 >= 0.0) 2 else 0) + + (if (v01 >= 0.0) 4 else 0) + + (if (v11 >= 0.0) 8 else 0) + + fun blend(v1: Double, v2: Double): Double { + if (useInterpolation) { + require(!v1.isNaN() && !v2.isNaN()) { + "Input values v1=$v1 or v2=$v2 are NaN, which is not allowed." + } + val f1 = min(v1, v2) + val f2 = max(v1, v2) + val v = (-f1) / (f2 - f1) + + require(v == v && v in 0.0..1.0) { + "Invalid value calculated during interpolation: v=$v" + } + + return if (f1 == v1) { + v + } else { + 1.0 - v + } + } else { + return 0.5 + } + } + + fun emitLine( + p00: Vector2D, p01: Vector2D, v00: Double, v01: Double, + p10: Vector2D, p11: Vector2D, v10: Double, v11: Double + ) { + val r0 = blend(v00, v01) + val r1 = blend(v10, v11) + + val v0 = p00.mix(p01, r0) + val v1 = p10.mix(p11, r1) + val l0 = LineSegment(v0, v1) + segmentsMap.getOrPut(v1) { mutableListOf() }.add(l0) + segmentsMap.getOrPut(v0) { mutableListOf() }.add(l0) + segments.add(l0) + } + + when (index) { + 0, 15 -> {} + 1, 15 xor 1 -> { + emitLine(p00, p01, v00, v01, p00, p10, v00, v10) + } + + 2, 15 xor 2 -> { + emitLine(p00, p10, v00, v10, p10, p11, v10, v11) + } + + 3, 15 xor 3 -> { + emitLine(p00, p01, v00, v01, p10, p11, v10, v11) + } + + 4, 15 xor 4 -> { + emitLine(p00, p01, v00, v01, p01, p11, v01, v11) + } + + 5, 15 xor 5 -> { + emitLine(p00, p10, v00, v10, p01, p11, v01, v11) + } + + 6, 15 xor 6 -> { + emitLine(p00, p01, v00, v01, p00, p10, v00, v10) + emitLine(p01, p11, v01, v11, p10, p11, v10, v11) + } + + 7, 15 xor 7 -> { + emitLine(p01, p11, v01, v11, p10, p11, v10, v11) + } + } + } + } + + val processedSegments = mutableSetOf() + val contours = mutableListOf() + for (segment in segments) { + if (segment in processedSegments) { + continue + } else { + val collected = mutableListOf() + var current: LineSegment? = segment + var closed = true + var lastVertex = Vector2D.INFINITY + do { + current!! + if (lastVertex.squaredDistanceTo(current.start) > 1E-5) { + collected.add(current.start) + } + lastVertex = current.start + processedSegments.add(current) + if (segmentsMap[current.start]!!.size < 2) { + closed = false + } + val hold = current + current = segmentsMap[current.start]?.firstOrNull { it !in processedSegments } + if (current == null) { + current = segmentsMap[hold.end]?.firstOrNull { it !in processedSegments } + } + } while (current != segment && current != null) + + contours.add(ShapeContour.fromPoints(collected, closed = closed)) + } + } + return contours +} \ No newline at end of file diff --git a/android/src/main/res/drawable/ic_launcher_background.xml b/android/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/android/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/src/main/res/drawable/ic_launcher_foreground.xml b/android/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/android/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/src/main/res/layout-port/activity_main.xml b/android/src/main/res/layout-port/activity_main.xml new file mode 100644 index 0000000..fb2cf36 --- /dev/null +++ b/android/src/main/res/layout-port/activity_main.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +