diff --git a/build.gradle b/build.gradle index 57072c07..c4c92fc2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,17 +5,23 @@ buildscript { name = "Minecraft Forge" url = "https://files.minecraftforge.net/maven" } + maven { + name = "SpongePowered" + url = "https://repo.spongepowered.org/maven" + } } dependencies { classpath "net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT" + classpath "org.spongepowered:mixingradle:0.6-SNAPSHOT" classpath "com.github.jengelman.gradle.plugins:shadow:4.0.0" } } apply plugin: "net.minecraftforge.gradle.forge" +apply plugin: "org.spongepowered.mixin" apply plugin: "com.github.johnrengelman.shadow" apply plugin: "maven-publish" -version = "1.1.0." + ('git rev-list --count HEAD'.execute().text.trim()) + "-1.12.2" +version = "2.0.0." + ('git rev-list --count HEAD'.execute().text.trim()) + "-1.12.2" group = "net.buildtheearth" archivesBaseName = "terraplusplus" @@ -29,6 +35,8 @@ minecraft { mappings = "stable_39" makeObfSourceJar = false + + coreMod = "net.buildtheearth.terraplusplus.asm.TerraPlusPlusMixinLoader" } configurations { @@ -37,14 +45,10 @@ configurations { } repositories { - maven { //used for leveldb + maven { name = "DaPorkchop_" url = "https://maven.daporkchop.net/" } - maven { //used for leveldb - name = "OpenCollab Snapshots" - url = "https://repo.opencollab.dev/snapshot/" - } maven { name = "JitPack" url = "https://jitpack.io/" @@ -52,25 +56,45 @@ repositories { } dependencies { - deobfProvided ("com.github.OpenCubicChunks.CubicChunks:cubicchunks:f71aafb9854466ac5ffa0ccd2796921684f1d8b1") { + deobfProvided("com.github.OpenCubicChunks.CubicChunks:cubicchunks:9dcb135c2990f1de0d0fd9e418121d3476288c11") { transitive = false } - deobfProvided ("com.github.OpenCubicChunks:CubicWorldGen:27de56d2f792513873584b2f8fd9f3082fb259ec") { + deobfProvided("com.github.OpenCubicChunks:CubicWorldGen:27de56d2f792513873584b2f8fd9f3082fb259ec") { transitive = false } - shade "org.apache.commons:commons-imaging:1.0-alpha2" + /*compileOnly("net.daporkchop:fp2:0.0.1-1.12.2-SNAPSHOT") { + transitive = false + }*/ + + shade "com.github.DaMatrix:commons-imaging:4c6c5dfe6401a884cb4cf53fb838c99e1dfb104c" shade "com.fasterxml.jackson.core:jackson-databind:2.11.2" - shade("net.daporkchop.lib:binary:0.5.5-SNAPSHOT") { + shade("net.daporkchop.lib:binary:0.5.8-SNAPSHOT") { exclude group: "io.netty" } + shade "net.daporkchop.lib:math:0.5.8-SNAPSHOT" - testCompile "junit:junit:4.12" + shade ("org.apache.sis.core:sis-referencing:1.3") { + exclude group: "jakarta.xml.bind" + } + + if ("true".equalsIgnoreCase(System.getProperty("idea.sync.active"))) { + //intellij is present - we don't want it to register mixin as an annotation processor, so we just add it as a standard dependency. + // if we don't do this, the intellij annotation processors will get totally screwed up + compile "org.spongepowered:mixin:0.8.1" + } else { + //register mixin as a standard annotation processor + annotationProcessor "org.spongepowered:mixin:0.8.1:processor" + } compileOnly "org.projectlombok:lombok:1.18.16" annotationProcessor "org.projectlombok:lombok:1.18.16" + + testCompile "junit:junit:4.12" + testCompileOnly "org.projectlombok:lombok:1.18.16" + testAnnotationProcessor "org.projectlombok:lombok:1.18.16" } processResources { @@ -92,11 +116,27 @@ processResources { } } +mixin { + defaultObfuscationEnv searge + add sourceSets.main, "terraplusplus.refmap.json" +} + shadowJar { classifier = null configurations = [project.configurations.shade] exclude 'module-info.class' + + manifest { + attributes( + "MixinConfigs": "terraplusplus.mixins.json", + "tweakClass": "org.spongepowered.asm.launch.MixinTweaker", + "TweakOrder": 0, + "FMLCorePluginContainsFMLMod": "true", + "FMLCorePlugin": "net.buildtheearth.terraplusplus.asm.TerraPlusPlusMixinLoader", + "ForceLoadAsMod": "true" + ) + } } build.dependsOn shadowJar diff --git a/src/main/java/net/buildtheearth/terraplusplus/EarthWorldType.java b/src/main/java/net/buildtheearth/terraplusplus/EarthWorldType.java index e4f786c6..cbecac2b 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/EarthWorldType.java +++ b/src/main/java/net/buildtheearth/terraplusplus/EarthWorldType.java @@ -9,6 +9,8 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiCreateWorld; import net.minecraft.world.World; +import net.minecraft.world.WorldProviderEnd; +import net.minecraft.world.WorldProviderHell; import net.minecraft.world.WorldProviderSurface; import net.minecraft.world.WorldServer; import net.minecraft.world.WorldType; @@ -33,7 +35,11 @@ public ICubeGenerator createCubeGenerator(World world) { @Override public BiomeProvider getBiomeProvider(World world) { - return EarthGeneratorSettings.parse(world.getWorldInfo().getGeneratorOptions()).biomeProvider(); + if (world instanceof WorldServer) { + return EarthGeneratorSettings.forWorld((WorldServer) world).biomeProvider(); + } else { + return EarthGeneratorSettings.parse(EarthGeneratorSettings.DEFAULT_SETTINGS).biomeProvider(); + } } @Override @@ -43,7 +49,9 @@ public IntRange calculateGenerationHeightRange(WorldServer world) { @Override public boolean hasCubicGeneratorForWorld(World w) { - return w.provider instanceof WorldProviderSurface; // an even more general way to check if it's overworld (need custom providers) + return w.provider instanceof WorldProviderSurface + || (TerraConfig.dimension.overrideNetherGeneration && w.provider instanceof WorldProviderHell) + || (TerraConfig.dimension.overrideEndGeneration && w.provider instanceof WorldProviderEnd); } @Override @@ -61,6 +69,11 @@ public double voidFadeMagnitude() { return 0; } + @Override + public double getHorizon(World world) { + return Integer.MIN_VALUE; + } + @Override @SideOnly(Side.CLIENT) public void onCustomizeButton(Minecraft mc, GuiCreateWorld guiCreateWorld) { diff --git a/src/main/java/net/buildtheearth/terraplusplus/TerraConfig.java b/src/main/java/net/buildtheearth/terraplusplus/TerraConfig.java index cecb217a..c40d0bd5 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/TerraConfig.java +++ b/src/main/java/net/buildtheearth/terraplusplus/TerraConfig.java @@ -1,5 +1,6 @@ package net.buildtheearth.terraplusplus; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.util.http.Http; import net.minecraftforge.common.config.Config; import net.minecraftforge.common.config.Config.Comment; @@ -12,6 +13,11 @@ @Mod.EventBusSubscriber(modid = TerraConstants.MODID) @Config(modid = TerraConstants.MODID) public class TerraConfig { + public static int[] lastVersion = { //this field might be used at some point + 1, 12, 2, //mc version + 2, 0, 0 //t++ version + }; + @Name("reduced_console_messages") @Comment({ "Removes all of TerraPlusPlus' messages which contain various links in the server console", "This is just if it seems to spam the console, it is purely for appearance" }) @@ -25,7 +31,17 @@ public class TerraConfig { public static boolean threeWater; @Comment({ - "Configure how terraplusplus' will retrieve OpenStreetMap data." + "Configure how terraplusplus will retrieve surface elevation data." + }) + public static ElevationOpts elevation = new ElevationOpts(); + + @Comment({ + "Configure how terraplusplus will retrieve tree cover data." + }) + public static TreeCoverOpts treeCover = new TreeCoverOpts(); + + @Comment({ + "Configure how terraplusplus will retrieve OpenStreetMap data." }) public static OSMOpts openstreetmap = new OSMOpts(); @@ -34,6 +50,8 @@ public class TerraConfig { }) public static HttpOpts http = new HttpOpts(); + public static DimensionOpts dimension = new DimensionOpts(); + @SubscribeEvent public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) { if (TerraConstants.MODID.equals(event.getModID())) { @@ -42,10 +60,27 @@ public static void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event } } + public static class ElevationOpts { + public String[] servers = { + "https://cloud.daporkchop.net/gis/dem/earth/" + }; + } + + public static class TreeCoverOpts { + public String[] servers = { + "https://cloud.daporkchop.net/gis/treecover2000/" + }; + } + public static class OSMOpts { + @Deprecated public String[] servers = { "https://cloud.daporkchop.net/gis/osm/0/" }; + + public String[] servers_v2 = { //different field name to avoid breaking old config files + "https://cloud.daporkchop.net/gis/osm/" + }; } public static class HttpOpts { @@ -62,9 +97,7 @@ public static class HttpOpts { public String[] maxConcurrentRequests = { "8: https://cloud.daporkchop.net/", "8: https://s3.amazonaws.com/", - "1: http://gis-treecover.wri.org/", - "1: https://overpass.kumi.systems/", - "1: https://lz4.overpass-api.de/" + "16: http://10.0.0.20/" }; @Comment({ @@ -79,4 +112,16 @@ public static class HttpOpts { }) public int cacheTTL = 1440; } + + public static class DimensionOpts { + @Comment({ + "Forces Terra++ generation in The End." + }) + public boolean overrideEndGeneration = false; + + @Comment({ + "Forces Terra++ generation in the Nether." + }) + public boolean overrideNetherGeneration = false; + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/TerraConstants.java b/src/main/java/net/buildtheearth/terraplusplus/TerraConstants.java deleted file mode 100644 index 942f57de..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/TerraConstants.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.buildtheearth.terraplusplus; - -import com.fasterxml.jackson.core.json.JsonReadFeature; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import net.buildtheearth.terraplusplus.dataset.osm.BlockStateParser; -import net.buildtheearth.terraplusplus.util.BiomeDeserializeMixin; -import net.buildtheearth.terraplusplus.util.BlockStateDeserializeMixin; -import net.minecraft.block.state.IBlockState; -import net.minecraft.world.biome.Biome; - -public class TerraConstants { - public static final String MODID = "terraplusplus"; - public static String VERSION = "(development_snapshot)"; - - public static String CC_VERSION = "unknown"; - - public static final String CHAT_PREFIX = "&2&lT++ &8&l> "; - public static final String defaultCommandNode = MODID + ".command."; - public static final String othersCommandNode = MODID + ".others"; - - public static final Gson GSON = new GsonBuilder() - .registerTypeAdapter(IBlockState.class, BlockStateParser.INSTANCE) - .create(); - - public static final JsonMapper JSON_MAPPER = JsonMapper.builder() - .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true) - .configure(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS, true) - .configure(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS, true) - .configure(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS, true) - .configure(JsonReadFeature.ALLOW_TRAILING_COMMA, true) - .addMixIn(Biome.class, BiomeDeserializeMixin.class) - .addMixIn(IBlockState.class, BlockStateDeserializeMixin.class) - .build(); - - /** - * Earth's circumference around the equator, in meters. - */ - public static final double EARTH_CIRCUMFERENCE = 40075017; - - /** - * Earth's circumference around the poles, in meters. - */ - public static final double EARTH_POLAR_CIRCUMFERENCE = 40008000; - - public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/TerraMod.java b/src/main/java/net/buildtheearth/terraplusplus/TerraMod.java index 90c8a430..82fee614 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/TerraMod.java +++ b/src/main/java/net/buildtheearth/terraplusplus/TerraMod.java @@ -7,6 +7,8 @@ import net.buildtheearth.terraplusplus.control.TerraTeleport; import net.buildtheearth.terraplusplus.provider.GenerationEventDenier; import net.buildtheearth.terraplusplus.provider.WaterDenier; +import net.buildtheearth.terraplusplus.util.TerraConstants; +import net.buildtheearth.terraplusplus.util.compat.fp2.TerraFP2CompatManager; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.Mod; @@ -40,6 +42,10 @@ public void construction(FMLConstructionEvent event) { } TerraConstants.CC_VERSION = Loader.instance().getIndexedModList().get(CubicChunks.MODID).getVersion(); + + if (Loader.instance().getIndexedModList().containsKey("fp2")) { + MinecraftForge.EVENT_BUS.register(TerraFP2CompatManager.class); + } } @EventHandler diff --git a/src/main/java/net/buildtheearth/terraplusplus/asm/TerraPlusPlusMixinLoader.java b/src/main/java/net/buildtheearth/terraplusplus/asm/TerraPlusPlusMixinLoader.java new file mode 100644 index 00000000..99582ab4 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/asm/TerraPlusPlusMixinLoader.java @@ -0,0 +1,48 @@ +package net.buildtheearth.terraplusplus.asm; + +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.mixin.Mixins; + +import javax.annotation.Nullable; +import java.util.Map; + +/** + * @author DaPorkchop_ + */ +public class TerraPlusPlusMixinLoader implements IFMLLoadingPlugin { + public TerraPlusPlusMixinLoader() { + try { + Class.forName("org.spongepowered.asm.launch.MixinBootstrap"); + MixinBootstrap.init(); + Mixins.addConfiguration("terraplusplus.mixins.json"); + } catch (ClassNotFoundException ignored) { + //this means cubic chunks isn't there, let forge show missing dependency screen + } + } + + @Override + public String[] getASMTransformerClass() { + return new String[0]; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Nullable + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + } + + @Override + public String getAccessTransformerClass() { + return null; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/asm/world/storage/MixinSaveHandler.java b/src/main/java/net/buildtheearth/terraplusplus/asm/world/storage/MixinSaveHandler.java new file mode 100644 index 00000000..78121b22 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/asm/world/storage/MixinSaveHandler.java @@ -0,0 +1,56 @@ +package net.buildtheearth.terraplusplus.asm.world.storage; + +import io.github.opencubicchunks.cubicchunks.cubicgen.common.world.storage.IWorldInfoAccess; +import net.buildtheearth.terraplusplus.EarthWorldType; +import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.storage.SaveHandler; +import net.minecraft.world.storage.WorldInfo; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * @author DaPorkchop_ + */ +@Mixin(SaveHandler.class) +public abstract class MixinSaveHandler { + @Shadow + @Final + private File worldDirectory; + + @Inject(method = "Lnet/minecraft/world/storage/SaveHandler;loadWorldInfo()Lnet/minecraft/world/storage/WorldInfo;", + at = @At("RETURN"), + require = 1) + private void terraplusplus_loadWorldInfo_loadSettingsFromFile(CallbackInfoReturnable ci) { + if (ci.getReturnValue() == null || !(ci.getReturnValue().getTerrainType() instanceof EarthWorldType)) { + return; + } + + Path settingsFile = EarthGeneratorSettings.settingsFile(this.worldDirectory.toPath()); + if (Files.exists(settingsFile)) { + ((IWorldInfoAccess) ci.getReturnValue()).setGeneratorOptions(EarthGeneratorSettings.readSettings(settingsFile)); + } else { + EarthGeneratorSettings.writeSettings(settingsFile, ci.getReturnValue().getGeneratorOptions()); + } + } + + @Inject(method = "Lnet/minecraft/world/storage/SaveHandler;saveWorldInfoWithPlayer(Lnet/minecraft/world/storage/WorldInfo;Lnet/minecraft/nbt/NBTTagCompound;)V", + at = @At("RETURN"), + require = 1) + private void terraplusplus_saveWorldInfoWithPlayer_writeSettingsToFile(WorldInfo worldInfo, NBTTagCompound nbt, CallbackInfo ci) { + if (!(worldInfo.getTerrainType() instanceof EarthWorldType)) { + return; + } + + EarthGeneratorSettings.writeSettings(EarthGeneratorSettings.settingsFile(this.worldDirectory.toPath()), worldInfo.getGeneratorOptions()); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/asm/world/storage/MixinWorldInfo.java b/src/main/java/net/buildtheearth/terraplusplus/asm/world/storage/MixinWorldInfo.java new file mode 100644 index 00000000..4d436352 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/asm/world/storage/MixinWorldInfo.java @@ -0,0 +1,33 @@ +package net.buildtheearth.terraplusplus.asm.world.storage; + +import net.buildtheearth.terraplusplus.EarthWorldType; +import net.minecraft.world.WorldType; +import net.minecraft.world.storage.WorldInfo; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +/** + * @author DaPorkchop_ + */ +@Mixin(WorldInfo.class) +public abstract class MixinWorldInfo { + @Shadow + private WorldType terrainType; + + @Shadow + private String generatorOptions; + + @Redirect(method = "Lnet/minecraft/world/storage/WorldInfo;updateTagCompound(Lnet/minecraft/nbt/NBTTagCompound;Lnet/minecraft/nbt/NBTTagCompound;)V", + at = @At(value = "FIELD", + target = "Lnet/minecraft/world/storage/WorldInfo;generatorOptions:Ljava/lang/String;"), + require = 1, allow = 1) + private String terraplusplus_updateTagCompound_dontStoreGeneratorOptionsToLevelDat(WorldInfo _this) { + if (this.terrainType instanceof EarthWorldType) { + return ""; + } else { + return this.generatorOptions; + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/GlobalParseRegistries.java b/src/main/java/net/buildtheearth/terraplusplus/config/GlobalParseRegistries.java index 432d9614..77e0b368 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/config/GlobalParseRegistries.java +++ b/src/main/java/net/buildtheearth/terraplusplus/config/GlobalParseRegistries.java @@ -1,42 +1,83 @@ package net.buildtheearth.terraplusplus.config; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.DatabindContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.experimental.UtilityClass; -import net.buildtheearth.terraplusplus.config.condition.AndDC; -import net.buildtheearth.terraplusplus.config.condition.DoubleCondition; -import net.buildtheearth.terraplusplus.config.condition.EqualDC; -import net.buildtheearth.terraplusplus.config.condition.GreaterThanDC; -import net.buildtheearth.terraplusplus.config.condition.LessThanDC; -import net.buildtheearth.terraplusplus.config.condition.NotDC; -import net.buildtheearth.terraplusplus.config.condition.OrDC; -import net.buildtheearth.terraplusplus.config.scalarparse.d.AddDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.d.DivideDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.d.DoubleScalarParser; -import net.buildtheearth.terraplusplus.config.scalarparse.d.FlipXDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.d.FlipZDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.d.FromIntDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.d.MultiplyDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.d.ParseFloatingPointTiffDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.d.ParseTerrariumPngDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.d.SwapAxesDSP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.AddISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.AndISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.FlipXISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.FlipZISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.GrayscaleExtractISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.IntScalarParser; -import net.buildtheearth.terraplusplus.config.scalarparse.i.ParseJpgISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.ParsePngISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.ParseTiffISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.RGBExtractISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.RequireOpaqueISP; -import net.buildtheearth.terraplusplus.config.scalarparse.i.SwapAxesISP; +import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; +import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValueBinaryOperator; +import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValueConstant; +import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValueTag; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapper; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapperAll; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapperAny; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapperCondition; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapperFirst; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapperNarrow; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapperNothing; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapperWide; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapper; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapperAll; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapperAny; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapperCondition; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapperConvertToLines; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapperDistance; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapperFill; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapperFirst; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapperNothing; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchCondition; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchConditionAnd; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchConditionId; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchConditionIntersects; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchConditionNot; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchConditionOr; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchConditionTag; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.format.TileFormat; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.format.TileFormatTerrariumPng; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.format.TileFormatTiff; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.mode.TileMode; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.mode.TileModeSimple; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.mode.TileModeSlippyMap; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionAll; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionBlock; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionConditionalRandom; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionNoTrees; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionOcean; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionWater; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionWeightAdd; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionWeightClamp; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionWeightGreaterThan; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunctionWeightLessThan; +import net.buildtheearth.terraplusplus.generator.biome.BiomeFilterConstant; +import net.buildtheearth.terraplusplus.generator.biome.BiomeFilterTerra121; +import net.buildtheearth.terraplusplus.generator.biome.BiomeFilterUserOverride; +import net.buildtheearth.terraplusplus.generator.biome.IEarthBiomeFilter; +import net.buildtheearth.terraplusplus.generator.data.DataBakerHeights; +import net.buildtheearth.terraplusplus.generator.data.DataBakerInitialBiomes; +import net.buildtheearth.terraplusplus.generator.data.DataBakerNullIsland; +import net.buildtheearth.terraplusplus.generator.data.DataBakerOSM; +import net.buildtheearth.terraplusplus.generator.data.DataBakerTreeCover; +import net.buildtheearth.terraplusplus.generator.data.IEarthDataBaker; +import net.buildtheearth.terraplusplus.generator.populate.IEarthPopulator; +import net.buildtheearth.terraplusplus.generator.populate.PopulatorBiomeDecoration; +import net.buildtheearth.terraplusplus.generator.populate.PopulatorSnow; +import net.buildtheearth.terraplusplus.generator.populate.PopulatorTrees; +import net.buildtheearth.terraplusplus.generator.settings.osm.GeneratorOSMSettings; +import net.buildtheearth.terraplusplus.generator.settings.osm.GeneratorOSMSettingsAll; +import net.buildtheearth.terraplusplus.generator.settings.osm.GeneratorOSMSettingsCustom; +import net.buildtheearth.terraplusplus.generator.settings.osm.GeneratorOSMSettingsDefault; +import net.buildtheearth.terraplusplus.generator.settings.osm.GeneratorOSMSettingsDisable; +import net.buildtheearth.terraplusplus.generator.settings.osm.GeneratorOSMSettingsToggle; import net.buildtheearth.terraplusplus.projection.EqualEarthProjection; import net.buildtheearth.terraplusplus.projection.EquirectangularProjection; import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.sis.SISProjectionWrapper; import net.buildtheearth.terraplusplus.projection.SinusoidalProjection; import net.buildtheearth.terraplusplus.projection.dymaxion.BTEDymaxionProjection; import net.buildtheearth.terraplusplus.projection.dymaxion.ConformalDynmaxionProjection; @@ -50,6 +91,10 @@ import net.buildtheearth.terraplusplus.projection.transform.ScaleProjectionTransform; import net.buildtheearth.terraplusplus.projection.transform.SwapAxesProjectionTransform; +import java.io.IOException; + +import static net.daporkchop.lib.common.util.PValidation.*; + /** * Identifies implementation classes by their type names. *

@@ -70,6 +115,7 @@ public class GlobalParseRegistries { .put("bte_conformal_dymaxion", BTEDymaxionProjection.class) .put("dymaxion", DymaxionProjection.class) .put("conformal_dymaxion", ConformalDynmaxionProjection.class) + .put("wkt", SISProjectionWrapper.class) //transformations .put("flip_horizontal", FlipHorizontalProjectionTransform.class) .put("flip_vertical", FlipVerticalProjectionTransform.class) @@ -78,48 +124,112 @@ public class GlobalParseRegistries { .put("swap_axes", SwapAxesProjectionTransform.class) .build(); - public final BiMap> DOUBLE_CONDITIONS = new BiMapBuilder>() - //conditions - .put("equal", EqualDC.class) - .put("greater_than", GreaterThanDC.class) - .put("less_than", LessThanDC.class) - //logical operators - .put("and", AndDC.class) - .put("not", NotDC.class) - .put("or", OrDC.class) + public final BiMap> TILE_FORMATS = new BiMapBuilder>() + .put("terrarium_png", TileFormatTerrariumPng.class) + .put("tiff", TileFormatTiff.class) + .build(); + + public final BiMap> TILE_MODES = new BiMapBuilder>() + .put("simple", TileModeSimple.class) + .put("slippy", TileModeSlippyMap.class) + .build(); + + public final BiMap> OSM_LINE_MAPPERS = new BiMapBuilder>() + //mergers + .put("all", LineMapperAll.class) + .put("any", LineMapperAny.class) + .put("first", LineMapperFirst.class) + //misc. + .put("condition", LineMapperCondition.class) + .put("nothing", LineMapperNothing.class) + //emitters + .put("narrow", LineMapperNarrow.class) + .put("wide", LineMapperWide.class) + .build(); + + public final BiMap> OSM_POLYGON_MAPPERS = new BiMapBuilder>() + //mergers + .put("all", PolygonMapperAll.class) + .put("any", PolygonMapperAny.class) + .put("first", PolygonMapperFirst.class) + //misc. + .put("condition", PolygonMapperCondition.class) + .put("convert_to_lines", PolygonMapperConvertToLines.class) + .put("nothing", PolygonMapperNothing.class) + //emitters + .put("rasterize_distance", PolygonMapperDistance.class) + .put("rasterize_fill", PolygonMapperFill.class) + .build(); + + public final BiMap> OSM_DVALUES = new BiMapBuilder>() + //math operators + .put("+", DValueBinaryOperator.Add.class) + .put("-", DValueBinaryOperator.Subtract.class) + .put("*", DValueBinaryOperator.Multiply.class) + .put("/", DValueBinaryOperator.Divide.class) + .put("floor_div", DValueBinaryOperator.FloorDiv.class) + .put("min", DValueBinaryOperator.Min.class) + .put("max", DValueBinaryOperator.Max.class) + //misc. + .put("constant", DValueConstant.class) + .put("tag", DValueTag.class) + .build(); + + public final BiMap> OSM_MATCH_CONDITIONS = new BiMapBuilder>() + //logical operations + .put("and", MatchConditionAnd.class) + .put("not", MatchConditionNot.class) + .put("or", MatchConditionOr.class) + //misc. + .put("id", MatchConditionId.class) + .put("intersects", MatchConditionIntersects.class) + .put("tag", MatchConditionTag.class) + .build(); + + public final BiMap> VECTOR_DRAW_FUNCTIONS = new BiMapBuilder>() + //mergers + .put("all", DrawFunctionAll.class) + //conditional + .put("conditional_random", DrawFunctionConditionalRandom.class) + //weight modifiers + .put("weight_add", DrawFunctionWeightAdd.class) + .put("weight_clamp", DrawFunctionWeightClamp.class) + //weight conditions + .put("weight_greater_than", DrawFunctionWeightGreaterThan.class) + .put("weight_less_than", DrawFunctionWeightLessThan.class) + //misc. + .put("block", DrawFunctionBlock.class) + .put("no_trees", DrawFunctionNoTrees.class) + .put("ocean", DrawFunctionOcean.class) + .put("water", DrawFunctionWater.class) + .build(); + + public final BiMap> GENERATOR_SETTINGS_OSM = new BiMapBuilder>() + .put("all", GeneratorOSMSettingsAll.class) + .put("custom", GeneratorOSMSettingsCustom.class) + .put("default", GeneratorOSMSettingsDefault.class) + .put("disable", GeneratorOSMSettingsDisable.class) + .put("toggle", GeneratorOSMSettingsToggle.class) + .build(); + + public final BiMap> GENERATOR_SETTINGS_BIOME_FILTER = new BiMapBuilder>() + .put("constant", BiomeFilterConstant.class) + .put("legacy_terra121", BiomeFilterTerra121.class) + .put("user_overrides", BiomeFilterUserOverride.class) .build(); - public final BiMap> SCALAR_PARSERS_DOUBLE = new BiMapBuilder>() - //arithmetic operators - .put("add", AddDSP.class) - .put("divide", DivideDSP.class) - .put("multiply", MultiplyDSP.class) - //conversion operators - .put("flip_x", FlipXDSP.class) - .put("flip_z", FlipZDSP.class) - .put("from_int", FromIntDSP.class) - .put("swap_axes", SwapAxesDSP.class) - //parse operators - .put("parse_png_terrarium", ParseTerrariumPngDSP.class) - .put("parse_tiff_fp", ParseFloatingPointTiffDSP.class) + public final BiMap> GENERATOR_SETTINGS_DATA_BAKER = new BiMapBuilder>() + .put("heights", DataBakerHeights.class) + .put("initial_biomes", DataBakerInitialBiomes.class) + .put("null_island", DataBakerNullIsland.class) + .put("osm", DataBakerOSM.class) + .put("tree_cover", DataBakerTreeCover.class) .build(); - public final BiMap> SCALAR_PARSERS_INT = new BiMapBuilder>() - //arithmetic operators - .put("add", AddISP.class) - //logical operators - .put("and", AndISP.class) - //conversion operators - .put("flip_x", FlipXISP.class) - .put("flip_z", FlipZISP.class) - .put("grayscale_extract", GrayscaleExtractISP.class) - .put("require_opaque", RequireOpaqueISP.class) - .put("rgb_extract", RGBExtractISP.class) - .put("swap_axes", SwapAxesISP.class) - //parse operators - .put("parse_jpg", ParseJpgISP.class) - .put("parse_png", ParsePngISP.class) - .put("parse_tiff", ParseTiffISP.class) + public final BiMap> GENERATOR_SETTINGS_POPULATOR = new BiMapBuilder>() + .put("biome_decorate", PopulatorBiomeDecoration.class) + .put("snow", PopulatorSnow.class) + .put("trees", PopulatorTrees.class) .build(); /** @@ -145,4 +255,37 @@ public BiMap build() { return this.delegate; } } + + /** + * Base implementation of a {@link com.fasterxml.jackson.databind.jsontype.TypeIdResolver} which uses a {@link BiMap}. + * + * @author DaPorkchop_ + */ + @RequiredArgsConstructor + public static abstract class TypeIdResolver extends TypeIdResolverBase { + @NonNull + protected final BiMap> registry; + + @Override + public String idFromValue(Object value) { + return this.idFromValueAndType(value, value.getClass()); + } + + @Override + public String idFromValueAndType(Object value, Class suggestedType) { + return this.registry.inverse().get(suggestedType); + } + + @Override + public JavaType typeFromId(DatabindContext context, String id) throws IOException { + Class clazz = this.registry.get(id); + checkArg(clazz != null, "unknown id: %s", id); + return context.getConfig().getTypeFactory().constructType(clazz); + } + + @Override + public JsonTypeInfo.Id getMechanism() { + return JsonTypeInfo.Id.CUSTOM; + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/TypedDeserializer.java b/src/main/java/net/buildtheearth/terraplusplus/config/TypedDeserializer.java index 38ba1290..f2cf616f 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/config/TypedDeserializer.java +++ b/src/main/java/net/buildtheearth/terraplusplus/config/TypedDeserializer.java @@ -1,7 +1,6 @@ package net.buildtheearth.terraplusplus.config; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/TypedSerializer.java b/src/main/java/net/buildtheearth/terraplusplus/config/TypedSerializer.java index aac4f264..bc764de5 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/config/TypedSerializer.java +++ b/src/main/java/net/buildtheearth/terraplusplus/config/TypedSerializer.java @@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import java.io.IOException; import java.util.Map; diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/condition/AndDC.java b/src/main/java/net/buildtheearth/terraplusplus/config/condition/AndDC.java deleted file mode 100644 index 7f3a9f37..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/condition/AndDC.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.buildtheearth.terraplusplus.config.condition; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.config.SingleProperty; - -/** - * @author DaPorkchop_ - */ -@RequiredArgsConstructor(onConstructor_ = { @JsonCreator(mode = JsonCreator.Mode.DELEGATING) }) -@JsonDeserialize -@Getter(onMethod_ = { @JsonValue }) -@SingleProperty -public class AndDC implements DoubleCondition { - @NonNull - protected final DoubleCondition[] delegates; - - @Override - public boolean test(double value) { - for (DoubleCondition delegate : this.delegates) { - if (!delegate.test(value)) { - return false; - } - } - return true; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/condition/DoubleCondition.java b/src/main/java/net/buildtheearth/terraplusplus/config/condition/DoubleCondition.java deleted file mode 100644 index a3ec208f..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/condition/DoubleCondition.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.buildtheearth.terraplusplus.config.condition; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; -import net.buildtheearth.terraplusplus.config.TypedDeserializer; -import net.buildtheearth.terraplusplus.config.TypedSerializer; - -import java.util.Map; -import java.util.function.DoublePredicate; - -/** - * A condition that accepts a {@code double} value and checks if it is applicable to a given argument. - * - * @author DaPorkchop_ - */ -@JsonDeserialize(using = DoubleCondition.Deserializer.class) -@JsonSerialize(using = DoubleCondition.Serializer.class) -@FunctionalInterface -public interface DoubleCondition extends DoublePredicate { - class Deserializer extends TypedDeserializer { - @Override - protected Map> registry() { - return GlobalParseRegistries.DOUBLE_CONDITIONS; - } - } - - class Serializer extends TypedSerializer { - @Override - protected Map, String> registry() { - return GlobalParseRegistries.DOUBLE_CONDITIONS.inverse(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/condition/GreaterThanDC.java b/src/main/java/net/buildtheearth/terraplusplus/config/condition/GreaterThanDC.java deleted file mode 100644 index 5d295526..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/condition/GreaterThanDC.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.buildtheearth.terraplusplus.config.condition; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.config.SingleProperty; - -/** - * @author DaPorkchop_ - */ -@RequiredArgsConstructor(onConstructor_ = { @JsonCreator(mode = JsonCreator.Mode.DELEGATING) }) -@JsonDeserialize -@Getter(onMethod_ = { @JsonValue }) -@SingleProperty -public class GreaterThanDC implements DoubleCondition { - protected final double value; - - @Override - public boolean test(double value) { - return value > this.value; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/condition/LessThanDC.java b/src/main/java/net/buildtheearth/terraplusplus/config/condition/LessThanDC.java deleted file mode 100644 index 1d6c6386..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/condition/LessThanDC.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.buildtheearth.terraplusplus.config.condition; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.config.SingleProperty; - -/** - * @author DaPorkchop_ - */ -@RequiredArgsConstructor(onConstructor_ = { @JsonCreator(mode = JsonCreator.Mode.DELEGATING) }) -@JsonDeserialize -@Getter(onMethod_ = { @JsonValue }) -@SingleProperty -public class LessThanDC implements DoubleCondition { - protected final double value; - - @Override - public boolean test(double value) { - return value < this.value; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/condition/OrDC.java b/src/main/java/net/buildtheearth/terraplusplus/config/condition/OrDC.java deleted file mode 100644 index f7b67c93..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/condition/OrDC.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.buildtheearth.terraplusplus.config.condition; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.config.SingleProperty; - -/** - * @author DaPorkchop_ - */ -@RequiredArgsConstructor(onConstructor_ = { @JsonCreator(mode = JsonCreator.Mode.DELEGATING) }) -@JsonDeserialize -@Getter(onMethod_ = { @JsonValue }) -@SingleProperty -public class OrDC implements DoubleCondition { - @NonNull - protected final DoubleCondition[] delegates; - - @Override - public boolean test(double value) { - for (DoubleCondition delegate : this.delegates) { - if (delegate.test(value)) { - return true; - } - } - return false; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/AddDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/AddDSP.java deleted file mode 100644 index 904e863f..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/AddDSP.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class AddDSP implements DoubleScalarParser { - protected final DoubleScalarParser delegate; - protected final double value; - - @JsonCreator - public AddDSP( - @JsonProperty(value = "delegate", required = true) @NonNull DoubleScalarParser delegate, - @JsonProperty(value = "value", required = true) double value) { - this.delegate = delegate; - this.value = value; - } - - @Override - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - double[] arr = this.delegate.parse(resolution, buffer); - double value = this.value; - for (int i = 0, len = resolution * resolution; i < len; i++) { - arr[i] += value; - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/DivideDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/DivideDSP.java deleted file mode 100644 index a14a391f..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/DivideDSP.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class DivideDSP implements DoubleScalarParser { - protected final DoubleScalarParser delegate; - protected final double value; - - @Getter(AccessLevel.NONE) - protected final double factor; - - @JsonCreator - public DivideDSP( - @JsonProperty(value = "delegate", required = true) @NonNull DoubleScalarParser delegate, - @JsonProperty(value = "value", required = true) double value) { - this.delegate = delegate; - this.value = value; - this.factor = 1.0d / value; - } - - @Override - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - double[] arr = this.delegate.parse(resolution, buffer); - double factor = this.factor; - for (int i = 0, len = resolution * resolution; i < len; i++) { - arr[i] *= factor; - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/DoubleScalarParser.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/DoubleScalarParser.java deleted file mode 100644 index 42d6a553..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/DoubleScalarParser.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.netty.buffer.ByteBuf; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; -import net.buildtheearth.terraplusplus.config.TypedDeserializer; -import net.buildtheearth.terraplusplus.config.TypedSerializer; - -import java.io.IOException; -import java.util.Map; - -/** - * Parses a square grid of {@code double} values from a binary representation. - * - * @author DaPorkchop_ - */ -@JsonDeserialize(using = DoubleScalarParser.Deserializer.class) -@JsonSerialize(using = DoubleScalarParser.Serializer.class) -@FunctionalInterface -public interface DoubleScalarParser { - double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException; - - class Deserializer extends TypedDeserializer { - @Override - protected Map> registry() { - return GlobalParseRegistries.SCALAR_PARSERS_DOUBLE; - } - } - - class Serializer extends TypedSerializer { - @Override - protected Map, String> registry() { - return GlobalParseRegistries.SCALAR_PARSERS_DOUBLE.inverse(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FlipXDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FlipXDSP.java deleted file mode 100644 index 9b1e366f..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FlipXDSP.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class FlipXDSP implements DoubleScalarParser { - protected final DoubleScalarParser delegate; - - @JsonCreator - public FlipXDSP(@JsonProperty(value = "delegate", required = true) @NonNull DoubleScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - double[] arr = this.delegate.parse(resolution, buffer); - for (int z = 0; z < resolution; z++) { - for (int x = 0, lim = resolution >> 1; x < lim; x++) { - int a = z * resolution + x; - int b = z * resolution + (resolution - x - 1); - double t = arr[a]; - arr[a] = arr[b]; - arr[b] = t; - } - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FlipZDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FlipZDSP.java deleted file mode 100644 index df5b3ae1..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FlipZDSP.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class FlipZDSP implements DoubleScalarParser { - protected final DoubleScalarParser delegate; - - @JsonCreator - public FlipZDSP(@JsonProperty(value = "delegate", required = true) @NonNull DoubleScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - double[] arr = this.delegate.parse(resolution, buffer); - for (int z = 0, lim = resolution >> 1; z < lim; z++) { - for (int x = 0; x < resolution; x++) { - int a = z * resolution + x; - int b = (resolution - z - 1) * resolution + x; - double t = arr[a]; - arr[a] = arr[b]; - arr[b] = t; - } - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FromIntDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FromIntDSP.java deleted file mode 100644 index 44bdb1d6..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/FromIntDSP.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.config.scalarparse.i.IntScalarParser; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class FromIntDSP implements DoubleScalarParser { - protected final IntScalarParser delegate; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public FromIntDSP(@JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] src = this.delegate.parse(resolution, buffer); - int len = resolution * resolution; - double[] dst = new double[len]; - for (int i = 0; i < len; i++) { - dst[i] = src[i] != Integer.MIN_VALUE ? src[i] : Double.NaN; - } - return dst; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/MultiplyDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/MultiplyDSP.java deleted file mode 100644 index fcf93c2a..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/MultiplyDSP.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class MultiplyDSP implements DoubleScalarParser { - protected final DoubleScalarParser delegate; - protected final double value; - - @JsonCreator - public MultiplyDSP( - @JsonProperty(value = "delegate", required = true) @NonNull DoubleScalarParser delegate, - @JsonProperty(value = "value", required = true) double value) { - this.delegate = delegate; - this.value = value; - } - - @Override - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - double[] arr = this.delegate.parse(resolution, buffer); - double value = this.value; - for (int i = 0, len = resolution * resolution; i < len; i++) { - arr[i] *= value; - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/ParseFloatingPointTiffDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/ParseFloatingPointTiffDSP.java deleted file mode 100644 index 805bfd33..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/ParseFloatingPointTiffDSP.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import lombok.NonNull; -import lombok.SneakyThrows; -import org.apache.commons.imaging.FormatCompliance; -import org.apache.commons.imaging.ImageReadException; -import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream; -import org.apache.commons.imaging.formats.tiff.TiffContents; -import org.apache.commons.imaging.formats.tiff.TiffDirectory; -import org.apache.commons.imaging.formats.tiff.TiffField; -import org.apache.commons.imaging.formats.tiff.TiffRasterData; -import org.apache.commons.imaging.formats.tiff.TiffReader; -import org.apache.commons.imaging.formats.tiff.constants.GdalLibraryTagConstants; - -import java.io.IOException; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -public class ParseFloatingPointTiffDSP implements DoubleScalarParser { - @Override - @SneakyThrows(ImageReadException.class) - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - TiffContents contents = new TiffReader(false) - .readDirectories(new ByteSourceInputStream(new ByteBufInputStream(buffer), ""), true, FormatCompliance.getDefault()); - TiffDirectory directory = contents.directories.get(0); - - TiffRasterData data = directory.getFloatingPointRasterData(null); - int w = data.getWidth(); - int h = data.getHeight(); - checkArg(w == resolution && h == resolution, "invalid image resolution: %dx%d (expected: %dx%3$d)", w, h, resolution); - - TiffField nodataField = directory.findField(GdalLibraryTagConstants.EXIF_TAG_GDAL_NO_DATA); - float nodata = nodataField != null ? Float.parseFloat(nodataField.getStringValue()) : Float.NaN; - - double[] out = new double[resolution * resolution]; - for (int i = 0; i < resolution * resolution; i++) { - float f = data.getData()[i]; - out[i] = f == nodata ? Double.NaN : f; - } - return out; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/ParseTerrariumPngDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/ParseTerrariumPngDSP.java deleted file mode 100644 index 5c580f00..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/ParseTerrariumPngDSP.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.config.scalarparse.i.IntScalarParser; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.IOException; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -public class ParseTerrariumPngDSP implements DoubleScalarParser { - @Override - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - BufferedImage image = ImageIO.read(new ByteBufInputStream(buffer)); - - int w = image.getWidth(); - int h = image.getHeight(); - checkArg(w == resolution && h == resolution, "invalid image resolution: %dx%d (expected: %dx%3$d)", w, h, resolution); - - int[] rgb = image.getRGB(0, 0, resolution, resolution, null, 0, resolution); - double[] out = new double[resolution * resolution]; - - for (int i = 0; i < resolution * resolution; i++) { - int c = rgb[i]; - if ((c >>> 24) != 0xFF) { //nodata - out[i] = Double.NaN; - } else { - out[i] = ((c & ~0xFF000000) - 0x00800000) * (1.0d / 256.0d); - } - } - - return out; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/SwapAxesDSP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/SwapAxesDSP.java deleted file mode 100644 index 8b71d2a7..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/d/SwapAxesDSP.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.d; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class SwapAxesDSP implements DoubleScalarParser { - protected final DoubleScalarParser delegate; - - @JsonCreator - public SwapAxesDSP(@JsonProperty(value = "delegate", required = true) @NonNull DoubleScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public double[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - double[] arr = this.delegate.parse(resolution, buffer); - for (int i = 1; i < resolution; i++) { - for (int j = 0; j < i; j++) { - int a = i * resolution + j; - int b = j * resolution + i; - double t = arr[a]; - arr[a] = arr[b]; - arr[b] = t; - } - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/AddISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/AddISP.java deleted file mode 100644 index e5a4d62f..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/AddISP.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class AddISP implements IntScalarParser { - protected final IntScalarParser delegate; - protected final int value; - - @JsonCreator - public AddISP( - @JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate, - @JsonProperty(value = "value", required = true) int value) { - this.delegate = delegate; - this.value = value; - } - - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] arr = this.delegate.parse(resolution, buffer); - int value = this.value; - for (int i = 0, len = resolution * resolution; i < len; i++) { - arr[i] += value; - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/AndISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/AndISP.java deleted file mode 100644 index 2c922aea..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/AndISP.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class AndISP implements IntScalarParser { - protected final IntScalarParser delegate; - protected final int mask; - - @JsonCreator - public AndISP( - @JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate, - @JsonProperty(value = "mask", required = true) int mask) { - this.delegate = delegate; - this.mask = mask; - } - - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] arr = this.delegate.parse(resolution, buffer); - int mask = this.mask; - for (int i = 0, len = resolution * resolution; i < len; i++) { - int v = arr[i]; - if (v != Integer.MIN_VALUE) { - arr[i] = v & mask; - } - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/FlipXISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/FlipXISP.java deleted file mode 100644 index 45a023ec..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/FlipXISP.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class FlipXISP implements IntScalarParser { - protected final IntScalarParser delegate; - - @JsonCreator - public FlipXISP(@JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] arr = this.delegate.parse(resolution, buffer); - for (int z = 0; z < resolution; z++) { - for (int x = 0, lim = resolution >> 1; x < lim; x++) { - int a = z * resolution + x; - int b = z * resolution + (resolution - x - 1); - int t = arr[a]; - arr[a] = arr[b]; - arr[b] = t; - } - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/FlipZISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/FlipZISP.java deleted file mode 100644 index 2b4b44ff..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/FlipZISP.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class FlipZISP implements IntScalarParser { - protected final IntScalarParser delegate; - - @JsonCreator - public FlipZISP(@JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] arr = this.delegate.parse(resolution, buffer); - for (int z = 0, lim = resolution >> 1; z < lim; z++) { - for (int x = 0; x < resolution; x++) { - int a = z * resolution + x; - int b = (resolution - z - 1) * resolution + x; - int t = arr[a]; - arr[a] = arr[b]; - arr[b] = t; - } - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/GrayscaleExtractISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/GrayscaleExtractISP.java deleted file mode 100644 index 0c05090d..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/GrayscaleExtractISP.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class GrayscaleExtractISP implements IntScalarParser { - protected final IntScalarParser delegate; - - @JsonCreator - public GrayscaleExtractISP(@JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] arr = this.delegate.parse(resolution, buffer); - for (int i = 0, len = resolution * resolution; i < len; i++) { - int val = arr[i]; - arr[i] = (val >>> 24) == 0xFF ? arr[i] & 0x000000FF : Integer.MIN_VALUE; - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/IntScalarParser.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/IntScalarParser.java deleted file mode 100644 index 5eded411..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/IntScalarParser.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.netty.buffer.ByteBuf; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; -import net.buildtheearth.terraplusplus.config.TypedDeserializer; -import net.buildtheearth.terraplusplus.config.TypedSerializer; - -import java.io.IOException; -import java.util.Map; - -/** - * Parses a square grid of {@code int} values from a binary representation. - * - * @author DaPorkchop_ - */ -@JsonDeserialize(using = IntScalarParser.Deserializer.class) -@JsonSerialize(using = IntScalarParser.Serializer.class) -@FunctionalInterface -public interface IntScalarParser { - int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException; - - class Deserializer extends TypedDeserializer { - @Override - protected Map> registry() { - return GlobalParseRegistries.SCALAR_PARSERS_INT; - } - } - - class Serializer extends TypedSerializer { - @Override - protected Map, String> registry() { - return GlobalParseRegistries.SCALAR_PARSERS_INT.inverse(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParseJpgISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParseJpgISP.java deleted file mode 100644 index 75b163b3..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParseJpgISP.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -public class ParseJpgISP extends ParsePngISP { - //we don't actually need to do anything different, since ImageIO does automatic type detection... -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParsePngISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParsePngISP.java deleted file mode 100644 index c9a79b42..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParsePngISP.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import lombok.NonNull; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.IOException; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -public class ParsePngISP implements IntScalarParser { - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - BufferedImage image = ImageIO.read(new ByteBufInputStream(buffer)); - - int w = image.getWidth(); - int h = image.getHeight(); - checkArg(w == resolution && h == resolution, "invalid image resolution: %dx%d (expected: %dx%3$d)", w, h, resolution); - - return image.getRGB(0, 0, resolution, resolution, null, 0, resolution); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParseTiffISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParseTiffISP.java deleted file mode 100644 index 44f88101..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/ParseTiffISP.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import lombok.NonNull; -import lombok.SneakyThrows; -import org.apache.commons.imaging.ImageReadException; -import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream; -import org.apache.commons.imaging.formats.tiff.TiffImageParser; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.Collections; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -public class ParseTiffISP implements IntScalarParser { - @Override - @SneakyThrows(ImageReadException.class) - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - BufferedImage image = new TiffImageParser().getBufferedImage(new ByteSourceInputStream(new ByteBufInputStream(buffer), ""), Collections.emptyMap()); - - int w = image.getWidth(); - int h = image.getHeight(); - checkArg(w == resolution && h == resolution, "invalid image resolution: %dx%d (expected: %dx%3$d)", w, h, resolution); - - return image.getRGB(0, 0, resolution, resolution, null, 0, resolution); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/RGBExtractISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/RGBExtractISP.java deleted file mode 100644 index 1fa843b5..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/RGBExtractISP.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class RGBExtractISP implements IntScalarParser { - protected final IntScalarParser delegate; - - @JsonCreator - public RGBExtractISP(@JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] arr = this.delegate.parse(resolution, buffer); - for (int i = 0, len = resolution * resolution; i < len; i++) { - int val = arr[i]; - arr[i] = (val >>> 24) == 0xFF ? arr[i] & 0x00FFFFFF : Integer.MIN_VALUE; - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/RequireOpaqueISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/RequireOpaqueISP.java deleted file mode 100644 index 8e1b420e..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/RequireOpaqueISP.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class RequireOpaqueISP implements IntScalarParser { - protected final IntScalarParser delegate; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public RequireOpaqueISP(@JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] arr = this.delegate.parse(resolution, buffer); - for (int i = 0, len = resolution * resolution; i < len; i++) { - if ((arr[i] >>> 24) != 0xFF) { //pixel is not fully transparent - arr[i] = Integer.MIN_VALUE; - } - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/SwapAxesISP.java b/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/SwapAxesISP.java deleted file mode 100644 index 171a5164..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/config/scalarparse/i/SwapAxesISP.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.buildtheearth.terraplusplus.config.scalarparse.i; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonDeserialize -@Getter(onMethod_ = { @JsonGetter }) -public class SwapAxesISP implements IntScalarParser { - protected final IntScalarParser delegate; - - @JsonCreator - public SwapAxesISP(@JsonProperty(value = "delegate", required = true) @NonNull IntScalarParser delegate) { - this.delegate = delegate; - } - - @Override - public int[] parse(int resolution, @NonNull ByteBuf buffer) throws IOException { - int[] arr = this.delegate.parse(resolution, buffer); - for (int i = 1; i < resolution; i++) { - for (int j = 0; j < i; j++) { - int a = i * resolution + j; - int b = j * resolution + i; - int t = arr[a]; - arr[a] = arr[b]; - arr[b] = t; - } - } - return arr; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/AdvancedEarthGui.java b/src/main/java/net/buildtheearth/terraplusplus/control/AdvancedEarthGui.java index d47fec8f..570b9215 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/AdvancedEarthGui.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/AdvancedEarthGui.java @@ -1,20 +1,27 @@ package net.buildtheearth.terraplusplus.control; +import com.fasterxml.jackson.core.JsonProcessingException; import io.github.opencubicchunks.cubicchunks.cubicgen.customcubic.CustomCubicWorldType; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.TerraConstants; +import lombok.SneakyThrows; import net.buildtheearth.terraplusplus.TerraMod; import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.GeographicProjectionHelper; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.transform.OffsetProjectionTransform; import net.buildtheearth.terraplusplus.projection.transform.ProjectionTransform; import net.buildtheearth.terraplusplus.projection.transform.ScaleProjectionTransform; +import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TerraConstants; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.buildtheearth.terraplusplus.util.math.matrix.TMatrices; import net.daporkchop.lib.common.function.io.IOSupplier; -import net.daporkchop.lib.common.ref.Ref; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; import net.daporkchop.lib.common.util.PArrays; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; @@ -24,25 +31,38 @@ import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiTextField; import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.resources.I18n; import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.TextFormatting; import net.minecraft.world.WorldType; import net.minecraftforge.client.event.GuiOpenEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.terraingen.DecorateBiomeEvent; import net.minecraftforge.event.terraingen.PopulateChunkEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.apache.sis.geometry.DirectPosition2D; +import org.apache.sis.parameter.DefaultParameterDescriptorGroup; +import org.apache.sis.parameter.DefaultParameterValueGroup; +import org.apache.sis.referencing.operation.matrix.Matrix2; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; +import org.opengis.parameter.GeneralParameterDescriptor; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; import javax.imageio.ImageIO; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -70,13 +90,17 @@ public class AdvancedEarthGui extends GuiScreen { protected static final int SRC_W = 2048; protected static final int SRC_H = 1024; - protected static final Ref SRC_CACHE = Ref.soft((IOSupplier) () -> - ImageIO.read(AdvancedEarthGui.class.getResource("map.png")).getRGB(0, 0, SRC_W, SRC_H, null, 0, SRC_W)); + protected static final Cached SRC_CACHE = Cached.threadLocal((IOSupplier) () -> + ImageIO.read(AdvancedEarthGui.class.getResource("map.png")).getRGB(0, 0, SRC_W, SRC_H, null, 0, SRC_W), + ReferenceStrength.SOFT); protected static final int VERTICAL_PADDING = 32; public static final ResourceLocation DIRECTIONS_TEXTURE = new ResourceLocation(TerraConstants.MODID, "textures/directions.png"); + protected static final int MOUSE_LEFT = 0; + protected static final int MOUSE_RIGHT = 1; + protected final GuiScreen parent; protected Consumer whenDone; @@ -200,7 +224,7 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawDefaultBackground(); this.drawCenteredString(this.fontRenderer, I18n.format(TerraConstants.MODID + ".gui.header"), this.width >> 1, VERTICAL_PADDING >> 1, 0xFFFFFFFF); - this.preview.draw(); + this.preview.draw(mouseX, mouseY); { //render list int y = VERTICAL_PADDING; @@ -246,11 +270,34 @@ public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.drawString(this.fontRenderer, I18n.format("terraplusplus.gui.world_size_header"), this.width - this.imgSize + 15, this.height - VERTICAL_PADDING - 30, 0xFFFFFFFF); this.drawString(this.fontRenderer, I18n.format("terraplusplus.gui.world_size_numbers", this.worldSizeX, this.worldSizeY), this.width - this.imgSize + 15, this.height - VERTICAL_PADDING - 15, 0xFFFFFFFF); - for(GuiButton button : this.nonTranslatedButtons) { + for (GuiButton button : this.nonTranslatedButtons) { button.drawButton(this.mc, mouseX, mouseY, partialTicks); } } + /** + * Draws a solid color line with the specified coordinates and color. + */ + public static void drawLine(int x0, int y0, int x1, int y1, int color) { + float f3 = (float)(color >> 24 & 255) / 255.0F; + float f = (float)(color >> 16 & 255) / 255.0F; + float f1 = (float)(color >> 8 & 255) / 255.0F; + float f2 = (float)(color & 255) / 255.0F; + + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder bufferbuilder = tessellator.getBuffer(); + GlStateManager.enableBlend(); + GlStateManager.disableTexture2D(); + GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + GlStateManager.color(f, f1, f2, f3); + bufferbuilder.begin(GL11.GL_LINES, DefaultVertexFormats.POSITION); + bufferbuilder.pos(x0, y0, 0.0D).endVertex(); + bufferbuilder.pos(x1, y1, 0.0D).endVertex(); + tessellator.draw(); + GlStateManager.enableTexture2D(); + GlStateManager.disableBlend(); + } + @Override public void updateScreen() { super.updateScreen(); @@ -269,14 +316,20 @@ protected void keyTyped(char typedChar, int keyCode) throws IOException { } } + protected static boolean propagateMousePressed(GuiButton button, Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + return button instanceof RightClickableGuiButton + ? ((RightClickableGuiButton) button).mousePressed(mc, mouseX, mouseY, mouseEvent) + : mouseEvent == MOUSE_LEFT && button.mousePressed(mc, mouseX, mouseY); + } + @Override public void mouseClicked(int mouseX, int mouseY, int mouseEvent) { this.lastClickMoveX = mouseX; this.lastClickMoveY = mouseY; - if (this.doneButton.mousePressed(this.mc, mouseX, mouseY)) { + if (propagateMousePressed(this.doneButton, this.mc, mouseX, mouseY, mouseEvent)) { this.whenDone.accept(this.settings.toString()); //save settings this.mc.displayGuiScreen(this.parent); //exit - } else if (this.cancelButton.mousePressed(this.mc, mouseX, mouseY)) { + } else if (propagateMousePressed(this.cancelButton, this.mc, mouseX, mouseY, mouseEvent)) { this.mc.displayGuiScreen(this.parent); //exit without saving } else { boolean updateQueued = false; @@ -288,7 +341,7 @@ public void mouseClicked(int mouseX, int mouseY, int mouseEvent) { } } for (GuiButton button : this.buttonList) { - if (button.mousePressed(this.mc, mouseX, mouseY + this.deltaY)) { + if (propagateMousePressed(button, this.mc, mouseX, mouseY + this.deltaY, mouseEvent)) { updateQueued = true; } } @@ -349,10 +402,10 @@ protected static class ProjectionEntry implements Entry { protected int height = 30; public ProjectionEntry(EarthGeneratorSettings settings, AdvancedEarthGui gui, int x, int y, int width) { - gui.addButton(new GuiButton(0, x + (width >> 1), y, width >> 1, 20, I18n.format(TerraConstants.MODID + ".gui.transformation.add")) { + gui.addButton(new RightClickableGuiButton(0, x + (width >> 1), y, width >> 1, 20, I18n.format(TerraConstants.MODID + ".gui.transformation.add")) { @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent) && mouseEvent == MOUSE_LEFT) { ProjectionEntry.this.entries.add(0, Transformation.values()[0].newSubEntry(ProjectionEntry.this, gui, 0, 0, 1)); return true; } @@ -453,16 +506,18 @@ protected void appendValue(StringBuilder out, int i) { } }; } - }; + }, + ; - static { + public Transformation next() { Transformation[] values = values(); - for (int i = 0; i < values.length; i++) { - values[i].next = values[(i + 1) % values.length]; - } + return values[(this.ordinal() + 1) % values.length]; } - private Transformation next; + public Transformation prev() { + Transformation[] values = values(); + return values[Math.floorMod(this.ordinal() - 1, values.length)]; + } protected TransformEntry newSubEntry(ProjectionEntry entry, AdvancedEarthGui gui, int x, int y, int width) { return new TransformEntry(this, entry, gui, x, y, width); @@ -493,8 +548,8 @@ public TransformEntry(Transformation transformation, ProjectionEntry entry, Adva this.upButton = gui.addButton(new EntryButton(x, y, 20, "\u25B2") { //up @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent) && mouseEvent == MOUSE_LEFT) { entry.entries.remove(TransformEntry.this); entry.entries.add(0, TransformEntry.this); return true; @@ -504,8 +559,8 @@ public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { }); this.downButton = gui.addButton(new EntryButton(x + 20, y, 20, "\u25BC") { //down @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent) && mouseEvent == MOUSE_LEFT) { entry.entries.remove(TransformEntry.this); entry.entries.add(entry.entries.size() - 1, TransformEntry.this); return true; @@ -515,8 +570,8 @@ public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { }); this.removeButton = gui.addButton(new EntryButton(x + 40, y, 20, "\u2716") { //remove @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent) && mouseEvent == MOUSE_LEFT) { entry.entries.remove(TransformEntry.this); return true; } @@ -526,10 +581,16 @@ public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { this.nameButton = gui.addButton(new EntryButton(x + 60, y, width - 60, I18n.format(TerraConstants.MODID + ".gui.transformation." + transformation.name())) { @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { - entry.entries.set(entry.entries.indexOf(TransformEntry.this), transformation.next.newSubEntry(entry, gui, 0, 0, 1)); - return true; + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent)) { + switch (mouseEvent) { + case MOUSE_LEFT: + entry.entries.set(entry.entries.indexOf(TransformEntry.this), transformation.next().newSubEntry(entry, gui, 0, 0, 1)); + return true; + case MOUSE_RIGHT: + entry.entries.set(entry.entries.indexOf(TransformEntry.this), transformation.prev().newSubEntry(entry, gui, 0, 0, 1)); + return true; + } } return false; } @@ -630,86 +691,142 @@ protected static class RootEntry implements SubEntry { protected int index; protected final String fieldName; - protected final Map properties; - protected final EntryTextField[] textFields; protected final EntryButton button; protected final Minecraft mc = Minecraft.getMinecraft(); + protected final DefaultParameterDescriptorGroup parameters; + protected final Map> parameterDescriptorsByName = new LinkedHashMap<>(); + protected final Map parameterValuesByName = new LinkedHashMap<>(); + + protected final String[] parameterNames; + protected final EntryButton[] buttons; + protected final EntryTextField[] textFields; + public RootEntry(GeographicProjection projection, AdvancedEarthGui gui, int x, int y, int width) { String projectionName = GlobalParseRegistries.PROJECTIONS.inverse().get(projection.getClass()); - this.initialIndex = this.index = PArrays.indexOf(PROJECTION_NAMES, projectionName); + this.initialIndex = this.index = PArrays.linearSearch(PROJECTION_NAMES, projectionName); this.button = gui.addButton(new EntryButton(x, y, width, I18n.format(this.fieldName = TerraConstants.MODID + ".gui.projection." + projectionName)) { @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { - RootEntry.this.index = (RootEntry.this.index + 1) % PROJECTION_NAMES.length; - return true; + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent)) { + switch (mouseEvent) { + case MOUSE_LEFT: + RootEntry.this.index = (RootEntry.this.index + 1) % PROJECTION_NAMES.length; + return true; + case MOUSE_RIGHT: + RootEntry.this.index = Math.floorMod(RootEntry.this.index - 1, PROJECTION_NAMES.length); + return true; + } } return false; } }); - this.properties = projection.properties(); + DefaultParameterValueGroup parameterValues = new DefaultParameterValueGroup(projection.parameters()); + this.parameters = DefaultParameterDescriptorGroup.castOrCopy(parameterValues.getDescriptor()); - int maxLen = this.properties.keySet().stream() - .map(s -> this.fieldName + '.' + s) - .map(I18n::format) - .mapToInt(gui.fontRenderer::getStringWidth) - .max().orElse(0) + 5; + for (GeneralParameterDescriptor parameter : this.parameters.descriptors()) { + this.parameterDescriptorsByName.put(parameter.getName().getCode(), (ParameterDescriptor) parameter); + this.parameterValuesByName.put(parameter.getName().getCode(), parameterValues.getValue((ParameterDescriptor) parameter)); + } + + int maxLen = this.parameterDescriptorsByName.keySet().stream() + .map(s -> this.fieldName + '.' + s) + .map(I18n::format) + .mapToInt(gui.fontRenderer::getStringWidth) + .max().orElse(0) + 5; + + this.parameterNames = this.parameterDescriptorsByName.keySet().toArray(new String[0]); + this.textFields = new EntryTextField[this.parameterNames.length]; + this.buttons = new EntryButton[this.parameterNames.length]; - this.textFields = new EntryTextField[this.properties.size()]; int i = 0; - for (Map.Entry entry : this.properties.entrySet()) { - this.textFields[i] = gui.addEntryTextField(x + maxLen, y + 20 + 2 + i * 24, width - maxLen - 2, 20); - this.textFields[i].setText(Objects.toString(entry.getValue())); + for (Map.Entry entry : this.parameterValuesByName.entrySet()) { + ParameterDescriptor descriptor = this.parameterDescriptorsByName.get(entry.getKey()); //yuck + + int componentX = x + maxLen; + int componentY = y + 20 + 2 + i * 24; + int componentW = width - maxLen - 2; + + if (CharSequence.class.isAssignableFrom(descriptor.getValueClass())) { + EntryTextField textField = gui.addEntryTextField(componentX, componentY, componentW, 20); + textField.setMaxStringLength(1 << 25); + textField.setText(Objects.toString(entry.getValue())); + this.textFields[i] = textField; + } else if (descriptor.getValidValues() != null || descriptor.getValueClass().isEnum()) { + Object[] values = descriptor.getValidValues() != null ? descriptor.getValidValues().toArray() : descriptor.getValueClass().getEnumConstants(); + int currentIndex = notNegative(PArrays.linearSearch(values, entry.getValue())); + + this.buttons[i] = gui.addButton(new EntryButton(componentX, componentY, componentW, I18n.format(this.fieldName + '.' + entry.getKey() + '.' + entry.getValue())) { + @Override + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent)) { + switch (mouseEvent) { + case MOUSE_LEFT: + RootEntry.this.parameterValuesByName.put(entry.getKey(), values[(currentIndex + 1) % values.length]); + return true; + case MOUSE_RIGHT: + RootEntry.this.parameterValuesByName.put(entry.getKey(), values[Math.floorMod(currentIndex - 1, values.length)]); + return true; + } + } + return false; + } + }); + } else { + throw new IllegalArgumentException(); + } i++; } } @Override public int height() { - return 20 + 2 + this.textFields.length * 24; + return 20 + 2 + this.parameterValuesByName.size() * 24; } @Override public void render(AdvancedEarthGui gui, int x, int y, int mouseX, int mouseY, int width) { int i = 0; - for (String s : this.properties.keySet()) { + for (String s : this.parameterDescriptorsByName.keySet()) { gui.fontRenderer.drawString(I18n.format(this.fieldName + '.' + s), x, y + 20 + 2 + i * 24 + (20 - 8) / 2, -1, true); i++; } - this.button.y = y; for (int j = 0; j < this.textFields.length; j++) { - this.textFields[j].y = y + 20 + 2 + j * 24; - this.textFields[j].actuallyDrawTextBox(); + if (this.textFields[j] != null) { + this.textFields[j].y = y + 20 + 2 + j * 24; + this.textFields[j].actuallyDrawTextBox(); + } } + for (int j = 0; j < this.buttons.length; j++) { + if (this.buttons[j] != null) { + this.buttons[j].y = y + 20 + 2 + j * 24; + this.buttons[j].actuallyDrawButton(this.mc, mouseX, mouseY); + } + } + this.button.y = y; this.button.actuallyDrawButton(this.mc, mouseX, mouseY); } @Override + @SneakyThrows(JsonProcessingException.class) public void toJson(StringBuilder out) { checkArg(out.length() == 0, "must be first element in json output!"); - out.append("{\"").append(PROJECTION_NAMES[this.index]).append("\":{"); + out.append("{\"").append(PROJECTION_NAMES[this.index]).append("\":"); + if (this.initialIndex == this.index) { - int i = 0; - for (Map.Entry entry : this.properties.entrySet()) { - if (i != 0) { - out.append(','); - } - out.append('"').append(entry.getKey()).append("\":"); - boolean num = entry.getValue() instanceof Number; - if (!num) { - out.append('"'); - } - out.append(this.textFields[i].getText()); - if (!num) { - out.append('"'); + for (int i = 0; i < this.textFields.length; i++) { + if (this.textFields[i] != null) { + this.parameterValuesByName.put(this.parameterNames[i], this.textFields[i].getText()); } - i++; } + out.append(TerraConstants.JSON_MAPPER.writeValueAsString(this.parameterValuesByName)); + } else { + out.append("{}"); } - out.append("}}"); + + out.append('}'); } } } @@ -722,10 +839,10 @@ public ToggleEntry(AdvancedEarthGui gui, int x, int y, int width, boolean value, this.touch = touch; this.value = value; - gui.addButton(new GuiButton(0, x, y, width, 20, I18n.format(TerraConstants.MODID + ".gui." + name) + ": " + I18n.format("options." + (value ? "on" : "off"))) { + gui.addButton(new RightClickableGuiButton(0, x, y, width, 20, I18n.format(TerraConstants.MODID + ".gui." + name) + ": " + I18n.format("options." + (value ? "on" : "off"))) { @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent) && mouseEvent == MOUSE_LEFT) { ToggleEntry.this.value = !ToggleEntry.this.value; return true; } @@ -761,10 +878,10 @@ public CWGEntry(EarthGeneratorSettings settings, AdvancedEarthGui gui, int x, in this.text = settings.cwg(); - gui.addButton(new GuiButton(0, x + width - 20, y, 20, 20, "...") { + gui.addButton(new RightClickableGuiButton(0, x + width - 20, y, 20, 20, "...") { @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent) && mouseEvent == MOUSE_LEFT) { GuiCreateWorld fakeParent = new GuiCreateWorld(null); fakeParent.chunkProviderSettingsJson = CWGEntry.this.text; @@ -807,10 +924,37 @@ public EarthGeneratorSettings touchSettings(EarthGeneratorSettings settings) { } } + /** + * A {@link GuiButton} whose {@link #mousePressed(Minecraft, int, int, int) mousePressed()} handler is also aware of the mouse button being pressed. + */ + private static class RightClickableGuiButton extends GuiButton { + public RightClickableGuiButton(int buttonId, int x, int y, String buttonText) { + super(buttonId, x, y, buttonText); + } + + public RightClickableGuiButton(int buttonId, int x, int y, int widthIn, int heightIn, String buttonText) { + super(buttonId, x, y, widthIn, heightIn, buttonText); + } + + /** + * @deprecated use {@link #mousePressed(Minecraft, int, int, int)} + */ + @Override + @Deprecated + public final boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { + //return this.mousePressed(mc, mouseX, mouseY, MOUSE_LEFT); + throw new UnsupportedOperationException("must use 4-argument mousePressed() overload!"); + } + + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + return super.mousePressed(mc, mouseX, mouseY); + } + } + /** * Did you have enough hacky stuff yet ? */ - private static class EntryButton extends GuiButton { + private static class EntryButton extends RightClickableGuiButton { public EntryButton(int x, int y, int width, String buttonText) { super(0, x, y, width, 20, buttonText); @@ -831,8 +975,6 @@ public void drawButton(Minecraft mc, int mouseX, int mouseY, float partialTicks) public void actuallyDrawButton(Minecraft mc, int mouseX, int mouseY) { super.drawButton(mc, mouseX, mouseY, 0); } - - } /** @@ -853,8 +995,6 @@ public void drawTextBox() { public void actuallyDrawTextBox() { super.drawTextBox(); } - - } public static class EnumSelectionListEntry> implements Entry { @@ -869,10 +1009,11 @@ public EnumSelectionListEntry(AdvancedEarthGui gui, int x, int y, int width, Set this.values = EnumSet.copyOf(values); for (T value : allValues) { - gui.addButton(new GuiButton(0, x + 2, y + this.height, width - 2, 20, value + ": " + I18n.format("options." + (this.values.contains(value) ? "off" : "on"))) { + boolean contains = this.values.contains(value); + gui.addButton(new RightClickableGuiButton(0, x + 2, y + this.height, width - 2, 20, value + ": " + (contains ? TextFormatting.RED : TextFormatting.GREEN) + I18n.format("options." + (contains ? "off" : "on"))) { @Override - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { - if (super.mousePressed(mc, mouseX, mouseY)) { + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY, int mouseEvent) { + if (super.mousePressed(mc, mouseX, mouseY, mouseEvent) && mouseEvent == MOUSE_LEFT) { if (!EnumSelectionListEntry.this.values.add(value)) { EnumSelectionListEntry.this.values.remove(value); } @@ -903,6 +1044,13 @@ public EarthGeneratorSettings touchSettings(EarthGeneratorSettings settings) { protected class ProjectionPreview { private int previewX, previewY, previewWidth, previewHeight, scaling; + + private double minX; + private double maxX; + private double minY; + private double maxY; + private double projScale; + private volatile boolean finish; private volatile boolean reset; private DynamicTexture previewTexture; @@ -1006,13 +1154,16 @@ private void projectTexture(int[] dst, int width, int height, GeographicProjecti // Scale should be able to fit whole earth inside texture double[] bounds = projection.bounds(); - double minX = min(bounds[0], bounds[2]); - double maxX = max(bounds[0], bounds[2]); - double minY = min(bounds[1], bounds[3]); - double maxY = max(bounds[1], bounds[3]); + double minX = this.minX = min(bounds[0], bounds[2]); + double maxX = this.maxX = max(bounds[0], bounds[2]); + double minY = this.minY = min(bounds[1], bounds[3]); + double maxY = this.maxY = max(bounds[1], bounds[3]); double dx = maxX - minX; double dy = maxY - minY; - double scale = max(dx, dy) / (double) Math.min(width, height); + double scale = this.projScale = max(dx, dy) / (double) Math.min(width, height); + + double[] buffer = new double[width * 2]; + MathTransform transform = SISHelper.findOperation(SISHelper.projectedCRS(this.projection), SISHelper.tppGeoCrs()).getMathTransform(); // Actually set map data for (int yi = 0; yi < height && !this.reset; yi++) { @@ -1021,24 +1172,42 @@ private void projectTexture(int[] dst, int width, int height, GeographicProjecti continue; } + int bufferedCount = 0; for (int xi = 0; xi < width; xi++) { double x = xi * scale + minX; if (x <= minX || x >= maxX) { //sample out of bounds, skip it continue; } - try { - double[] projected = projection.toGeo(x, y); - int xPixel = (int) (((projected[0] + 180.0d) * (SRC_W / 360.0d))); - int yPixel = (int) (((projected[1] + 90.0d) * (SRC_H / 180.0d))); - if (xPixel < 0 || xPixel >= SRC_W || yPixel < 0 || yPixel >= SRC_H) { //projected sample is out of bounds - continue; - } + buffer[bufferedCount * 2 + 0] = x; + buffer[bufferedCount * 2 + 1] = y; + bufferedCount++; + } + + SISHelper.transformManyPointsWithOutOfBoundsNaN(transform, buffer, 0, buffer, 0, bufferedCount); + + for (int xi = 0, bufferIndex = 0; xi < width; xi++) { + double x = xi * scale + minX; + if (x <= minX || x >= maxX) { //sample out of bounds, skip it + continue; + } + + double projectedX = buffer[bufferIndex * 2 + 0]; + double projectedY = buffer[bufferIndex * 2 + 1]; + bufferIndex++; - dst[yi * width + xi] = this.src[(SRC_H - yPixel - 1) * SRC_W + xPixel]; - } catch (OutOfProjectionBoundsException ignored) { - //sample out of bounds, skip it + if (Double.isNaN(projectedX) || Double.isNaN(projectedY)) { + continue; } + + int xPixel = (int) (((projectedX + 180.0d) * (SRC_W / 360.0d))); + int yPixel = (int) (((projectedY + 90.0d) * (SRC_H / 180.0d))); + + if (xPixel < 0 || xPixel >= SRC_W || yPixel < 0 || yPixel >= SRC_H) { //projected sample is out of bounds + continue; + } + + dst[yi * width + xi] = this.src[(SRC_H - yPixel - 1) * SRC_W + xPixel]; } synchronized (this.textureNeedsUpdate) { this.textureNeedsUpdate.set(true); @@ -1067,7 +1236,7 @@ private void projectTexture(int[] dst, int width, int height, GeographicProjecti } } - public void draw() { + public void draw(int mouseX, int mouseY) { synchronized (this.textureNeedsUpdate) { if (this.textureNeedsUpdate.get()) { this.previewTexture.updateDynamicTexture(); @@ -1078,7 +1247,56 @@ public void draw() { } Minecraft.getMinecraft().getTextureManager().bindTexture(DIRECTIONS_TEXTURE); drawScaledCustomSizeModalRect(this.previewX + this.previewWidth - 64, this.previewY, 0, 0, 64, 64, 64, 64, 64, 64); + + int relativeMouseX = (mouseX - this.previewX) * this.scaling; + int relativeMouseY = (mouseY - this.previewY) * this.scaling; + if (relativeMouseX >= 0 && relativeMouseY >= 0 && relativeMouseX < this.previewWidth * this.scaling && relativeMouseY < this.previewHeight * this.scaling) { + double projX = relativeMouseX * this.projScale + this.minX; + double projY = relativeMouseY * this.projScale + this.minY; + + MathTransform transform = SISHelper.findOperation(TerraConstants.TPP_GEO_CRS, SISHelper.projectedCRS(this.projection)).getMathTransform(); + + try { + if (true) { + double[] geo = this.projection.toGeo(projX, projY); + + this.drawDerivativeLines( + Matrix2.castOrCopy(transform.derivative(new DirectPosition2D(geo[0], geo[1]))), + GeographicProjectionHelper.defaultDerivative(this.projection, geo[0], geo[1], true), + mouseX, mouseY, false); + } else { + this.drawDerivativeLines( + Matrix2.castOrCopy(transform.inverse().derivative(new DirectPosition2D(projX, projY))), + GeographicProjectionHelper.defaultDerivative(this.projection, projX, projY, false), + mouseX, mouseY, true); + } + } catch (TransformException e) { + //ignored + } + } } + private void drawDerivativeLines(Matrix2 transformDerivative, Matrix2 defaultDerivative, int mouseX, int mouseY, boolean invert) { + if (invert) { + TMatrices.invertFast(defaultDerivative, defaultDerivative); + TMatrices.invertFast(transformDerivative, transformDerivative); + } + + final double lineLength = 32.0d; + + defaultDerivative.normalizeColumns(); + TMatrices.scaleFast(defaultDerivative, lineLength, defaultDerivative); + + GlStateManager.glLineWidth(5.0f); + drawLine(mouseX, mouseY, (int) (mouseX + defaultDerivative.m00), (int) (mouseY + defaultDerivative.m10), 0x8800FF00); + drawLine(mouseX, mouseY, (int) (mouseX + defaultDerivative.m01), (int) (mouseY + defaultDerivative.m11), 0x88FF0000); + + transformDerivative.normalizeColumns(); + TMatrices.scaleFast(transformDerivative, lineLength, transformDerivative); + + GlStateManager.glLineWidth(2.0f); + drawLine(mouseX, mouseY, (int) (mouseX + transformDerivative.m00), (int) (mouseY + transformDerivative.m10), 0xFF00FF00); + drawLine(mouseX, mouseY, (int) (mouseX + transformDerivative.m01), (int) (mouseY + transformDerivative.m11), 0xFFFF0000); + } } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/Command.java b/src/main/java/net/buildtheearth/terraplusplus/control/Command.java index 060a43e1..908c07c0 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/Command.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/Command.java @@ -1,6 +1,6 @@ package net.buildtheearth.terraplusplus.control; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.minecraft.command.CommandBase; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayer; diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/PresetEarthGui.java b/src/main/java/net/buildtheearth/terraplusplus/control/PresetEarthGui.java index 60564d97..30ae4fc8 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/PresetEarthGui.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/PresetEarthGui.java @@ -1,6 +1,6 @@ package net.buildtheearth.terraplusplus.control; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; @@ -86,7 +86,6 @@ public void initGui() { this.presetTextField = new GuiTextField(0, this.fontRenderer, this.width / 2 - 100, 40, 200, 20); this.presetTextField.setMaxStringLength(Integer.MAX_VALUE); this.setSettingsJson(this.settings); - } @Override @@ -117,6 +116,7 @@ protected void keyTyped(char typedChar, int keyCode) throws IOException { EarthGeneratorSettings.parseUncached(newText); this.presetTextField.setTextColor(0xFFFFFFFF); this.doneButton.enabled = true; + this.setSettingsJson(newText); } catch (Exception e) { this.presetTextField.setTextColor(0xFFFF6060); this.doneButton.enabled = false; diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/TerraCommand.java b/src/main/java/net/buildtheearth/terraplusplus/control/TerraCommand.java index 7163b379..32db2e94 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/TerraCommand.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/TerraCommand.java @@ -1,7 +1,7 @@ package net.buildtheearth.terraplusplus.control; import com.google.common.collect.Lists; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.control.fragments.FragmentManager; import net.buildtheearth.terraplusplus.control.fragments.terra.TerraConvertFragment; import net.buildtheearth.terraplusplus.control.fragments.terra.TerraDistortionFragment; diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/TerraTeleport.java b/src/main/java/net/buildtheearth/terraplusplus/control/TerraTeleport.java index 2d83d304..50bd2c60 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/TerraTeleport.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/TerraTeleport.java @@ -2,15 +2,14 @@ import io.github.opencubicchunks.cubicchunks.api.worldgen.ICubeGenerator; import io.github.opencubicchunks.cubicchunks.core.server.CubeProviderServer; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.dataset.IScalarDataset; import net.buildtheearth.terraplusplus.generator.EarthGenerator; import net.buildtheearth.terraplusplus.generator.EarthGeneratorPipelines; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.ChatUtil; -import net.buildtheearth.terraplusplus.util.TranslateUtil; +import net.buildtheearth.terraplusplus.util.TerraUtils; import net.buildtheearth.terraplusplus.util.geo.CoordinateParseUtils; -import net.buildtheearth.terraplusplus.util.geo.LatLng; +import net.buildtheearth.terraplusplus.util.geo.GeographicCoordinates; import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayerMP; @@ -87,16 +86,16 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args } double altitude = Double.NaN; - LatLng defaultCoords = CoordinateParseUtils.parseVerbatimCoordinates(this.getRawArguments(args).trim()); + GeographicCoordinates defaultCoords = CoordinateParseUtils.parseVerbatimCoordinates(this.getRawArguments(args).trim()); if (defaultCoords == null) { - LatLng possiblePlayerCoords = CoordinateParseUtils.parseVerbatimCoordinates(this.getRawArguments(this.selectArray(args, 1))); + GeographicCoordinates possiblePlayerCoords = CoordinateParseUtils.parseVerbatimCoordinates(this.getRawArguments(this.selectArray(args, 1))); if (possiblePlayerCoords != null) { defaultCoords = possiblePlayerCoords; } } - LatLng possibleHeightCoords = CoordinateParseUtils.parseVerbatimCoordinates(this.getRawArguments(this.inverseSelectArray(args, args.length - 1))); + GeographicCoordinates possibleHeightCoords = CoordinateParseUtils.parseVerbatimCoordinates(this.getRawArguments(this.inverseSelectArray(args, args.length - 1))); if (possibleHeightCoords != null) { defaultCoords = possibleHeightCoords; try { @@ -106,7 +105,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args } } - LatLng possibleHeightNameCoords = CoordinateParseUtils.parseVerbatimCoordinates(this.getRawArguments(this.inverseSelectArray(this.selectArray(args, 1), this.selectArray(args, 1).length - 1))); + GeographicCoordinates possibleHeightNameCoords = CoordinateParseUtils.parseVerbatimCoordinates(this.getRawArguments(this.inverseSelectArray(this.selectArray(args, 1), this.selectArray(args, 1).length - 1))); if (possibleHeightNameCoords != null) { defaultCoords = possibleHeightNameCoords; try { @@ -124,9 +123,9 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args double[] proj; try { - proj = terrain.projection.fromGeo(defaultCoords.getLng(), defaultCoords.getLat()); + proj = terrain.projection.fromGeo(defaultCoords.longitudeDegrees(), defaultCoords.latitudeDegrees()); } catch (Exception e) { - sender.sendMessage(ChatUtil.combine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".error.numbers"))); + sender.sendMessage(TerraUtils.combine(TextFormatting.RED, TerraUtils.translate(TerraConstants.MODID + ".error.numbers"))); return; } @@ -135,10 +134,10 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args try { altFuture = terrain.datasets .getCustom(EarthGeneratorPipelines.KEY_DATASET_HEIGHTS) - .getAsync(defaultCoords.getLng(), defaultCoords.getLat()) + .getAsync(defaultCoords.longitudeDegrees(), defaultCoords.latitudeDegrees()) .thenApply(a -> a + 1.0d); } catch (OutOfProjectionBoundsException e) { //out of bounds, notify user - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".error.numbers"))); + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.RED, TerraUtils.translate(TerraConstants.MODID + ".error.numbers"))); return; } } else { @@ -149,18 +148,18 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args receivers.add((EntityPlayerMP) sender); } List finalReceivers = receivers; - LatLng finalDefaultCoords = defaultCoords; + GeographicCoordinates finalDefaultCoords = defaultCoords; altFuture.thenAccept(s -> FMLCommonHandler.instance().getMinecraftServerInstance().addScheduledTask(() -> { for (EntityPlayerMP p : finalReceivers) { if (p.getName().equalsIgnoreCase(sender.getName())) { - p.sendMessage(ChatUtil.titleAndCombine(TextFormatting.GRAY, "Teleporting to ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.getLat()), - TextFormatting.GRAY, ", ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.getLng()))); + p.sendMessage(TerraUtils.titleAndCombine(TextFormatting.GRAY, "Teleporting to ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.latitudeDegrees()), + TextFormatting.GRAY, ", ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.longitudeDegrees()))); } else if (!sender.getName().equalsIgnoreCase("@")) { - p.sendMessage(ChatUtil.titleAndCombine(TextFormatting.GRAY, "Summoned to ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.getLat()), - TextFormatting.GRAY, ", ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.getLng()), TextFormatting.GRAY, " by ", TextFormatting.RED, sender.getDisplayName())); + p.sendMessage(TerraUtils.titleAndCombine(TextFormatting.GRAY, "Summoned to ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.latitudeDegrees()), + TextFormatting.GRAY, ", ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.longitudeDegrees()), TextFormatting.GRAY, " by ", TextFormatting.RED, sender.getDisplayName())); } else { - p.sendMessage(ChatUtil.titleAndCombine(TextFormatting.GRAY, "Summoned to ", TextFormatting.BLUE, TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.getLat()), - TextFormatting.GRAY, ", ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.getLng()), TextFormatting.GRAY)); + p.sendMessage(TerraUtils.titleAndCombine(TextFormatting.GRAY, "Summoned to ", TextFormatting.BLUE, TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.latitudeDegrees()), + TextFormatting.GRAY, ", ", TextFormatting.BLUE, this.formatDecimal(finalDefaultCoords.longitudeDegrees()), TextFormatting.GRAY)); } p.setPositionAndUpdate(proj[0], s, proj[1]); } @@ -222,9 +221,9 @@ private String getRawArguments(String[] args) { private void usage(ICommandSender sender) { if (this.hasPermission(sender, TerraConstants.othersCommandNode)) { - sender.sendMessage(ChatUtil.combine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.defaultCommandNode + "tpll.others.usage"))); + sender.sendMessage(TerraUtils.combine(TextFormatting.RED, TerraUtils.translate(TerraConstants.defaultCommandNode + "tpll.others.usage"))); } else { - sender.sendMessage(ChatUtil.combine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.defaultCommandNode + "tpll.usage"))); + sender.sendMessage(TerraUtils.combine(TextFormatting.RED, TerraUtils.translate(TerraConstants.defaultCommandNode + "tpll.usage"))); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/FragmentManager.java b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/FragmentManager.java index ea2880bf..21b7e6f1 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/FragmentManager.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/FragmentManager.java @@ -3,7 +3,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import net.buildtheearth.terraplusplus.control.Command; -import net.buildtheearth.terraplusplus.util.ChatUtil; +import net.buildtheearth.terraplusplus.util.TerraUtils; import net.minecraft.command.ICommandSender; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; @@ -46,12 +46,12 @@ protected void executeFragment(MinecraftServer server, ICommandSender sender, St private void displayCommands(ICommandSender sender) { for (int i = 0; i < 2; i++) { - sender.sendMessage(ChatUtil.combine("")); + sender.sendMessage(TerraUtils.combine("")); } - sender.sendMessage(ChatUtil.combine(TextFormatting.DARK_GRAY + "" + TextFormatting.STRIKETHROUGH, "================", + sender.sendMessage(TerraUtils.combine(TextFormatting.DARK_GRAY + "" + TextFormatting.STRIKETHROUGH, "================", TextFormatting.DARK_GREEN + "" + TextFormatting.BOLD, " Terra++ ", TextFormatting.DARK_GRAY + "" + TextFormatting.STRIKETHROUGH, "================")); - sender.sendMessage(ChatUtil.combine("")); + sender.sendMessage(TerraUtils.combine("")); for (CommandFragment f : this.singleFragments) { ITextComponent message = new TextComponentString(this.commandBase).setStyle(new Style().setColor(TextFormatting.YELLOW)); @@ -70,8 +70,8 @@ private void displayCommands(ICommandSender sender) { message.appendSibling(new TextComponentString(f.getPurpose()).setStyle(new Style().setColor(TextFormatting.BLUE))); sender.sendMessage(message); } - sender.sendMessage(ChatUtil.combine("")); - sender.sendMessage(ChatUtil.combine(TextFormatting.DARK_GRAY + "" + TextFormatting.STRIKETHROUGH, "==========================================")); + sender.sendMessage(TerraUtils.combine("")); + sender.sendMessage(TerraUtils.combine(TextFormatting.DARK_GRAY + "" + TextFormatting.STRIKETHROUGH, "==========================================")); } @Override diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraConvertFragment.java b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraConvertFragment.java index 90e9c07e..cd9e3119 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraConvertFragment.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraConvertFragment.java @@ -2,13 +2,12 @@ import io.github.opencubicchunks.cubicchunks.api.worldgen.ICubeGenerator; import io.github.opencubicchunks.cubicchunks.core.server.CubeProviderServer; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.control.fragments.CommandFragment; import net.buildtheearth.terraplusplus.generator.EarthGenerator; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.ChatUtil; -import net.buildtheearth.terraplusplus.util.TranslateUtil; +import net.buildtheearth.terraplusplus.util.TerraUtils; import net.minecraft.command.ICommandSender; import net.minecraft.server.MinecraftServer; import net.minecraft.util.text.TextFormatting; @@ -22,14 +21,14 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args IChunkProvider cp = world.getChunkProvider(); if (!(cp instanceof CubeProviderServer)) { - sender.sendMessage(ChatUtil.getNotCC()); + sender.sendMessage(TerraUtils.getNotCC()); return; } ICubeGenerator gen = ((CubeProviderServer) cp).getCubeGenerator(); if (!(gen instanceof EarthGenerator)) { - sender.sendMessage(ChatUtil.getNotTerra()); + sender.sendMessage(TerraUtils.getNotTerra()); return; } @@ -37,7 +36,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args GeographicProjection projection = terrain.projection; if (args.length < 2) { - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.RED, "Usage: /terra convert ")); + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.RED, "Usage: /terra convert ")); return; } @@ -47,7 +46,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args x = Double.parseDouble(args[0]); y = Double.parseDouble(args[1]); } catch (Exception e) { - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".error.numbers"))); + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.RED, TerraUtils.translate(TerraConstants.MODID + ".error.numbers"))); return; } @@ -60,7 +59,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args } catch (OutOfProjectionBoundsException e) { e.printStackTrace(); } - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.GRAY, "Result: ", + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.GRAY, "Result: ", TextFormatting.BLUE, c[0], TextFormatting.GRAY, ", ", TextFormatting.BLUE, c[1])); } else { try { @@ -68,7 +67,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args } catch (OutOfProjectionBoundsException e) { e.printStackTrace(); } - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.GRAY, "Result: ", + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.GRAY, "Result: ", TextFormatting.BLUE, c[1], TextFormatting.GRAY, ", ", TextFormatting.BLUE, c[0])); } } @@ -80,7 +79,7 @@ public String[] getName() { @Override public String getPurpose() { - return TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.convert.purpose").getUnformattedComponentText(); + return TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.convert.purpose").getUnformattedComponentText(); } @Override diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraDistortionFragment.java b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraDistortionFragment.java index a71921aa..769d9ee3 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraDistortionFragment.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraDistortionFragment.java @@ -2,13 +2,12 @@ import io.github.opencubicchunks.cubicchunks.api.worldgen.ICubeGenerator; import io.github.opencubicchunks.cubicchunks.core.server.CubeProviderServer; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.control.fragments.CommandFragment; import net.buildtheearth.terraplusplus.generator.EarthGenerator; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.ChatUtil; -import net.buildtheearth.terraplusplus.util.TranslateUtil; +import net.buildtheearth.terraplusplus.util.TerraUtils; import net.minecraft.command.ICommandSender; import net.minecraft.server.MinecraftServer; import net.minecraft.util.text.TextFormatting; @@ -22,14 +21,14 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args IChunkProvider cp = world.getChunkProvider(); if (!(cp instanceof CubeProviderServer)) { - sender.sendMessage(ChatUtil.getNotCC()); + sender.sendMessage(TerraUtils.getNotCC()); return; } ICubeGenerator gen = ((CubeProviderServer) cp).getCubeGenerator(); if (!(gen instanceof EarthGenerator)) { - sender.sendMessage(ChatUtil.getNotTerra()); + sender.sendMessage(TerraUtils.getNotTerra()); return; } @@ -45,11 +44,11 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args } if (c == null || Double.isNaN(c[0])) { - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.where.notproj"))); + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.RED, TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.where.notproj"))); return; } - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.GRAY, "Distortion:")); - sender.sendMessage(ChatUtil.combine(TextFormatting.RED, TranslateUtil.format(TerraConstants.MODID + ".command.terra.tissot", Math.sqrt(Math.abs(c[0])), c[1] * 180.0 / Math.PI))); + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.GRAY, "Distortion:")); + sender.sendMessage(TerraUtils.combine(TextFormatting.RED, TerraUtils.format(TerraConstants.MODID + ".command.terra.tissot", Math.sqrt(Math.abs(c[0])), c[1] * 180.0 / Math.PI))); } @Override @@ -59,7 +58,7 @@ public String[] getName() { @Override public String getPurpose() { - return TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.distortion.purpose").getUnformattedComponentText(); + return TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.distortion.purpose").getUnformattedComponentText(); } @Override diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraInfoFragment.java b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraInfoFragment.java index 5265206d..347b35dd 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraInfoFragment.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraInfoFragment.java @@ -1,9 +1,8 @@ package net.buildtheearth.terraplusplus.control.fragments.terra; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.control.fragments.CommandFragment; -import net.buildtheearth.terraplusplus.util.ChatUtil; -import net.buildtheearth.terraplusplus.util.TranslateUtil; +import net.buildtheearth.terraplusplus.util.TerraUtils; import net.minecraft.command.ICommandSender; import net.minecraft.server.MinecraftServer; import net.minecraft.util.text.TextComponentString; @@ -12,7 +11,7 @@ public class TerraInfoFragment extends CommandFragment { @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) { - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.GREEN, "Terra++ v", TerraConstants.VERSION, + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.GREEN, "Terra++ v", TerraConstants.VERSION, TextFormatting.GRAY, " by the ", TextFormatting.BLUE, "BTE Development Community")); sender.sendMessage(new TextComponentString(TextFormatting.GOLD + "Original mod by orangeadam3 and shejan0")); } @@ -24,7 +23,7 @@ public String[] getName() { @Override public String getPurpose() { - return TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.info.purpose").getUnformattedComponentText(); + return TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.info.purpose").getUnformattedComponentText(); } @Override diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraWhereFragment.java b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraWhereFragment.java index 7716c029..3f761c61 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraWhereFragment.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraWhereFragment.java @@ -2,14 +2,13 @@ import io.github.opencubicchunks.cubicchunks.api.worldgen.ICubeGenerator; import io.github.opencubicchunks.cubicchunks.core.server.CubeProviderServer; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.control.fragments.CommandFragment; import net.buildtheearth.terraplusplus.generator.EarthGenerator; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CardinalDirection; -import net.buildtheearth.terraplusplus.util.ChatUtil; -import net.buildtheearth.terraplusplus.util.TranslateUtil; +import net.buildtheearth.terraplusplus.util.TerraUtils; import net.minecraft.command.ICommandSender; import net.minecraft.entity.Entity; import net.minecraft.server.MinecraftServer; @@ -26,7 +25,7 @@ public class TerraWhereFragment extends CommandFragment { @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) { if (sender instanceof MinecraftServer && args.length < 1) { - sender.sendMessage(ChatUtil.getPlayerOnly()); + sender.sendMessage(TerraUtils.getPlayerOnly()); return; } @@ -34,14 +33,14 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args IChunkProvider cp = world.getChunkProvider(); if (!(cp instanceof CubeProviderServer)) { - sender.sendMessage(ChatUtil.getNotCC()); + sender.sendMessage(TerraUtils.getNotCC()); return; } ICubeGenerator gen = ((CubeProviderServer) cp).getCubeGenerator(); if (!(gen instanceof EarthGenerator)) { - sender.sendMessage(ChatUtil.getNotTerra()); + sender.sendMessage(TerraUtils.getNotTerra()); return; } @@ -54,7 +53,7 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args e = sender.getEntityWorld().getPlayerEntityByName(args[0]); } if (e == null) { - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".error.unknownplayer"))); + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.RED, TerraUtils.translate(TerraConstants.MODID + ".error.unknownplayer"))); return; } @@ -76,22 +75,22 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args result = null; azimuth = Float.NaN; } - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.GRAY, "Location of ", TextFormatting.BLUE, senderName)); + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.GRAY, "Location of ", TextFormatting.BLUE, senderName)); if (result == null || Double.isNaN(result[0])) { - sender.sendMessage(ChatUtil.combine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.where.notproj"))); + sender.sendMessage(TerraUtils.combine(TextFormatting.RED, TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.where.notproj"))); return; } if (!Float.isFinite(azimuth)) { - sender.sendMessage(ChatUtil.combine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.where.notproj"))); + sender.sendMessage(TerraUtils.combine(TextFormatting.RED, TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.where.notproj"))); return; } - sender.sendMessage(ChatUtil.combine(TextFormatting.GRAY, "Location: ", TextFormatting.BLUE, result[1], + sender.sendMessage(TerraUtils.combine(TextFormatting.GRAY, "Location: ", TextFormatting.BLUE, result[1], TextFormatting.GRAY, ", ", TextFormatting.BLUE, result[0]).setStyle(new Style().setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TextComponentString("Click to copy"))) .setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, String.format("%s, %s", result[1], result[0]))))); - sender.sendMessage(ChatUtil.combine(TextFormatting.GRAY, "Facing: ", TextFormatting.BLUE, CardinalDirection.azimuthToFacing(azimuth).realName(), TextFormatting.GRAY, " (", TextFormatting.BLUE, azimuth, TextFormatting.GRAY, ")")); - sender.sendMessage(ChatUtil.combine(new TextComponentString("Open in Google Maps").setStyle(new Style().setUnderlined(true).setColor(TextFormatting.YELLOW) + sender.sendMessage(TerraUtils.combine(TextFormatting.GRAY, "Facing: ", TextFormatting.BLUE, CardinalDirection.azimuthToFacing(azimuth).realName(), TextFormatting.GRAY, " (", TextFormatting.BLUE, azimuth, TextFormatting.GRAY, ")")); + sender.sendMessage(TerraUtils.combine(new TextComponentString("Open in Google Maps").setStyle(new Style().setUnderlined(true).setColor(TextFormatting.YELLOW) .setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TextComponentString("Open map"))) .setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.google.com/maps/search/?api=1&query=" + result[1] + "," + result[0]))))); @@ -104,7 +103,7 @@ public String[] getName() { @Override public String getPurpose() { - return TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.where.purpose").getUnformattedComponentText(); + return TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.where.purpose").getUnformattedComponentText(); } @Override diff --git a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraWorldFragment.java b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraWorldFragment.java index 6d7d0d96..9bd5e2af 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraWorldFragment.java +++ b/src/main/java/net/buildtheearth/terraplusplus/control/fragments/terra/TerraWorldFragment.java @@ -2,12 +2,11 @@ import io.github.opencubicchunks.cubicchunks.api.worldgen.ICubeGenerator; import io.github.opencubicchunks.cubicchunks.core.server.CubeProviderServer; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.control.fragments.CommandFragment; import net.buildtheearth.terraplusplus.generator.EarthGenerator; import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; -import net.buildtheearth.terraplusplus.util.ChatUtil; -import net.buildtheearth.terraplusplus.util.TranslateUtil; +import net.buildtheearth.terraplusplus.util.TerraUtils; import net.minecraft.command.ICommandSender; import net.minecraft.server.MinecraftServer; import net.minecraft.util.text.ITextComponent; @@ -19,7 +18,7 @@ public class TerraWorldFragment extends CommandFragment { @Override public void execute(MinecraftServer server, ICommandSender sender, String[] args) { - sender.sendMessage(ChatUtil.titleAndCombine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.world.header"))); + sender.sendMessage(TerraUtils.titleAndCombine(TextFormatting.RED, TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.world.header"))); World world = sender.getEntityWorld(); IChunkProvider cp = world.getChunkProvider(); @@ -38,10 +37,10 @@ public void execute(MinecraftServer server, ICommandSender sender, String[] args EarthGeneratorSettings settings = ((EarthGenerator) gen).settings; - sender.sendMessage(ChatUtil.combine(TextFormatting.BLUE, "World Type: ", TextFormatting.GREEN, "Earth World")); - sender.sendMessage(ChatUtil.combine(TextFormatting.RESET)); + sender.sendMessage(TerraUtils.combine(TextFormatting.BLUE, "World Type: ", TextFormatting.GREEN, "Earth World")); + sender.sendMessage(TerraUtils.combine(TextFormatting.RESET)); - sender.sendMessage(ChatUtil.combine(TextFormatting.BLUE, "Projection: ", TextFormatting.GREEN, settings.projection().toString())); + sender.sendMessage(TerraUtils.combine(TextFormatting.BLUE, "Projection: ", TextFormatting.GREEN, settings.projection().toString())); sender.sendMessage(this.boolComponent("Default Heights", settings.useDefaultHeights())); sender.sendMessage(this.boolComponent("Default Trees", settings.useDefaultTreeCover())); } @@ -53,7 +52,7 @@ public String[] getName() { @Override public String getPurpose() { - return TranslateUtil.translate(TerraConstants.MODID + ".fragment.terra.world.purpose").getUnformattedComponentText(); + return TerraUtils.translate(TerraConstants.MODID + ".fragment.terra.world.purpose").getUnformattedComponentText(); } @Override @@ -67,6 +66,6 @@ public String getPermission() { } private ITextComponent boolComponent(String name, boolean value) { - return ChatUtil.combine(TextFormatting.BLUE, name, ": ", (value ? TextFormatting.GREEN + "Yes" : TextFormatting.RED + "No")); + return TerraUtils.combine(TextFormatting.BLUE, name, ": ", (value ? TextFormatting.GREEN + "Yes" : TextFormatting.RED + "No")); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/BlendMode.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/BlendMode.java index 9a1263a3..36c08008 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/BlendMode.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/BlendMode.java @@ -4,6 +4,8 @@ import lombok.AllArgsConstructor; import lombok.NonNull; import net.buildtheearth.terraplusplus.util.IntToDoubleBiFunction; +import net.daporkchop.lib.math.grid.Grid2d; +import net.daporkchop.lib.math.interpolation.CubicInterpolation; import static java.lang.Math.*; import static net.daporkchop.lib.common.math.PMath.*; @@ -33,83 +35,48 @@ public double get(double scaledX, double scaledZ, @NonNull IntToDoubleBiFunction }, @JsonAlias("SMOOTH") //old name CUBIC(-0.5d, 3) { - /** - * Lerping produces visible square patches. - * - * Fade-curve lerping doesn't work well on steep slopes. - * - * Standard splines require 16 control points. - * - * This requires only 9 control points to produce a smooth interpolation. - * - * @author K.jpg - */ - double compute(double fx, double fy, double v00, double v01, double v02, double v10, double v11, double v12, double v20, double v21, double v22) { - // Smooth fade curve. Using this directly in a lerp wouldn't work well for steep slopes. - // But using it here with gradient ramps does a better job. - double xFade = fx * fx * (3.0d - 2.0d * fx); - double yFade = fy * fy * (3.0d - 2.0d * fy); - - // Centerpoints of each square. The interpolator meets these values exactly. - double vAA = (v00 + v01 + v10 + v11) * 0.25d; - double vAB = (v01 + v02 + v11 + v12) * 0.25d; - double vBA = (v10 + v20 + v11 + v21) * 0.25d; - double vBB = (v11 + v21 + v12 + v22) * 0.25d; - - // Slopes at each centerpoint. - // We "should" divide by 2. But we divide x and y by 2 instead for the same result. - double vAAx = ((v10 + v11) - (v00 + v01)); - double vAAy = ((v01 + v11) - (v00 + v10)); - double vABx = ((v11 + v12) - (v01 + v02)); - double vABy = ((v02 + v12) - (v01 + v11)); - double vBAx = ((v20 + v21) - (v10 + v11)); - double vBAy = ((v11 + v21) - (v10 + v20)); - double vBBx = ((v21 + v22) - (v11 + v12)); - double vBBy = ((v12 + v22) - (v11 + v21)); - - // This is where we correct for the doubled slopes. - // Note that it means we need x-0.5 instead of x-1. - fx *= 0.5d; - fy *= 0.5d; - double ix = fx - 0.5d; - double iy = fy - 0.5d; - - // extrapolate gradients and blend - double blendXA = (1.0d - xFade) * (vAA + vAAx * fx + vAAy * fy) + xFade * (vBA + vBAx * ix + vBAy * fy); - double blendXB = (1.0d - xFade) * (vAB + vABx * fx + vABy * iy) + xFade * (vBB + vBBx * ix + vBBy * iy); - return (1.0d - yFade) * blendXA + yFade * blendXB; - } - @Override public double get(double scaledX, double scaledZ, @NonNull IntToDoubleBiFunction sampler) { - double x = scaledX - 0.5d; - double z = scaledZ - 0.5d; - - //get the corners surrounding this block - int sampleX = (int) floor(x); - int sampleZ = (int) floor(z); - - double fx = x - sampleX; - double fz = z - sampleZ; - - double v00 = sampler.apply(sampleX, sampleZ); - double v01 = sampler.apply(sampleX, sampleZ + 1); - double v02 = sampler.apply(sampleX, sampleZ + 2); - double v10 = sampler.apply(sampleX + 1, sampleZ); - double v11 = sampler.apply(sampleX + 1, sampleZ + 1); - double v12 = sampler.apply(sampleX + 1, sampleZ + 2); - double v20 = sampler.apply(sampleX + 2, sampleZ); - double v21 = sampler.apply(sampleX + 2, sampleZ + 1); - double v22 = sampler.apply(sampleX + 2, sampleZ + 2); - - //Compute smooth 9-point interpolation on this block - double result = this.compute(fx, fz, v00, v01, v02, v10, v11, v12, v20, v21, v22); - - if (result > 0.0d && v00 <= 0.0d && v10 <= 0.0d && v20 <= 0.0d && v21 <= 0.0d && v11 <= 0.0d && v01 <= 0.0d && v02 <= 0.0d && v12 <= 0.0d && v22 <= 0.0d) { - return 0.0d; //anti ocean ridges - } - - return result; + //TODO: optimize this (eliminate allocation) + return CubicInterpolation.instance().getInterpolated(scaledX, scaledZ, new Grid2d() { + @Override + public int startX() { + return Integer.MIN_VALUE; + } + + @Override + public int endX() { + return Integer.MAX_VALUE; + } + + @Override + public int startY() { + return Integer.MIN_VALUE; + } + + @Override + public int endY() { + return Integer.MAX_VALUE; + } + + @Override + public double getD(int x, int y) { + return sampler.apply(x, y); + } + + @Override + public int getI(int x, int y) { + return (int) sampler.apply(x, y); + } + + @Override + public void setD(int x, int y, double val) { + } + + @Override + public void setI(int x, int y, int val) { + } + }); } }; diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/IElementDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/IElementDataset.java index 638198ee..e3472a97 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/IElementDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/IElementDataset.java @@ -4,19 +4,33 @@ import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import java.lang.reflect.Array; import java.util.concurrent.CompletableFuture; +import static net.daporkchop.lib.common.util.PorkUtil.*; + /** * A dataset consisting of arbitrary elements. * * @author DaPorkchop_ */ public interface IElementDataset { + /** + * Gets an {@link IElementDataset} which contains no elements. + * + * @param type the class of the element type + * @return an {@link IElementDataset} which contains no elements + */ + static IElementDataset empty(@NonNull Class type) { + return (bounds, zoom) -> CompletableFuture.completedFuture(uncheckedCast(Array.newInstance(type, 0))); + } + /** * Gets all of the elements that intersect the given bounding box. * * @param bounds the bounding box + * @param zoom the zoom level * @return a {@link CompletableFuture} which will be completed with the elements */ - CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds) throws OutOfProjectionBoundsException; + CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds, int zoom) throws OutOfProjectionBoundsException; } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/IScalarDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/IScalarDataset.java index 94db1d68..8e8e994f 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/IScalarDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/IScalarDataset.java @@ -1,10 +1,9 @@ package net.buildtheearth.terraplusplus.dataset; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.scalar.ConfigurableDoubleTiledDataset; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; import java.util.concurrent.CompletableFuture; @@ -13,16 +12,7 @@ * * @author DaPorkchop_ */ -@JsonDeserialize(as = ConfigurableDoubleTiledDataset.class) public interface IScalarDataset { - /** - * @param point the point - * @see #getAsync(double, double) - */ - default CompletableFuture getAsync(@NonNull double[] point) throws OutOfProjectionBoundsException { - return this.getAsync(point[0], point[1]); - } - /** * Asynchronously gets a single value at the given point. * @@ -40,4 +30,19 @@ default CompletableFuture getAsync(@NonNull double[] point) throws OutOf * @return a {@link CompletableFuture} which will be completed with the values */ CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds, int sizeX, int sizeZ) throws OutOfProjectionBoundsException; + + /** + * Asynchronously gets a bunch of values at the given coordinates. + * + * @param points an array of points + * @return a {@link CompletableFuture} which will be completed with the values + */ + CompletableFuture getAsync(@NonNull PointArray2D points) throws OutOfProjectionBoundsException; + + /** + * @return the number of meters between sample points (in geographic/unprojected coordinate space) + */ + default double[] degreesPerSample() { + throw new UnsupportedOperationException(); + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/TiledDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/TiledDataset.java index bf11ada5..78db6e35 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/TiledDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/TiledDataset.java @@ -4,11 +4,11 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import net.buildtheearth.terraplusplus.projection.GeographicProjection; -import net.minecraft.util.math.ChunkPos; +import net.buildtheearth.terraplusplus.util.TilePos; @RequiredArgsConstructor @Getter -public abstract class TiledDataset extends Dataset { +public abstract class TiledDataset extends Dataset { @NonNull protected final GeographicProjection projection; protected final double tileSize; diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/TiledHttpDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/TiledHttpDataset.java index 2bd89a7d..8017ddae 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/TiledHttpDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/TiledHttpDataset.java @@ -4,9 +4,9 @@ import io.netty.buffer.ByteBuf; import lombok.NonNull; import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.http.Http; import net.daporkchop.lib.common.misc.string.PStrings; -import net.minecraft.util.math.ChunkPos; import java.util.Arrays; import java.util.Map; @@ -20,7 +20,7 @@ public TiledHttpDataset(@NonNull GeographicProjection projection, double tileSiz super(projection, tileSize); } - protected abstract String[] urls(int tileX, int tileZ); + protected abstract String[] urls(int tileX, int tileZ, int zoom); protected void addProperties(int tileX, int tileZ, @NonNull ImmutableMap.Builder builder) { builder.put("x", String.valueOf(tileX)) @@ -34,19 +34,19 @@ protected void addProperties(int tileX, int tileZ, @NonNull ImmutableMap.Builder protected abstract T decode(int tileX, int tileZ, @NonNull ByteBuf data) throws Exception; @Override - public CompletableFuture load(@NonNull ChunkPos pos) throws Exception { - String[] urls = this.urls(pos.x, pos.z); + public CompletableFuture load(@NonNull TilePos pos) throws Exception { + String[] urls = this.urls(pos.x(), pos.z(), pos.zoom()); if (urls == null || urls.length == 0) { //no urls for tile return CompletableFuture.completedFuture(null); } ImmutableMap.Builder builder = ImmutableMap.builder(); - this.addProperties(pos.x, pos.z, builder); + this.addProperties(pos.x(), pos.z(), builder); Map properties = builder.build(); return Http.getFirst( Arrays.stream(urls).map(url -> Http.formatUrl(properties, url)).toArray(String[]::new), - data -> this.decode(pos.x, pos.z, data)); + data -> this.decode(pos.x(), pos.z(), data)); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/AbstractBuiltinDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/AbstractBuiltinDataset.java index 81163a6c..6d9ab933 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/AbstractBuiltinDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/AbstractBuiltinDataset.java @@ -4,9 +4,12 @@ import net.buildtheearth.terraplusplus.dataset.IScalarDataset; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; +import net.daporkchop.lib.common.pool.array.ArrayAllocator; import java.util.concurrent.CompletableFuture; +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; import static net.daporkchop.lib.common.util.PValidation.*; /** @@ -61,5 +64,33 @@ public CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds, }); } + @Override + public CompletableFuture getAsync(@NonNull PointArray2D points) throws OutOfProjectionBoundsException { + if (points.size() == 0) { //no input points -> no output points, ez + return CompletableFuture.completedFuture(new double[0]); + } + + return CompletableFuture.supplyAsync(() -> { + double scaleX = this.scaleX; + double scaleY = this.scaleY; + + int size = points.size(); + + //read coordinate values into an array + ArrayAllocator alloc = DOUBLE_ALLOC.get(); + double[] pointsBuffer = alloc.atLeast(points.totalValueSize()); + points.points(pointsBuffer, 0); + + //sample the value at each point and write them into a new array + double[] out = new double[size]; + for (int i = 0; i < size; i++) { + out[i] = this.get(pointsBuffer[i * 2] * scaleX, pointsBuffer[i * 2 + 1] * scaleY); + } + + alloc.release(pointsBuffer); + return out; + }); + } + protected abstract double get(double x, double y); } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/Climate.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/Climate.java index 01471552..354a8927 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/Climate.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/Climate.java @@ -7,7 +7,8 @@ import net.buildtheearth.terraplusplus.util.IntToDoubleBiFunction; import net.daporkchop.lib.binary.oio.StreamUtil; import net.daporkchop.lib.common.function.io.IOSupplier; -import net.daporkchop.lib.common.ref.Ref; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; import java.io.InputStream; @@ -15,7 +16,7 @@ public abstract class Climate extends AbstractBuiltinDataset implements IntToDou public static final int COLS = 720; public static final int ROWS = 360; - private static final Ref DATA_CACHE = Ref.soft((IOSupplier) () -> { + private static final Cached DATA_CACHE = Cached.global((IOSupplier) () -> { ByteBuf buf; try (InputStream in = new LzmaInputStream(Climate.class.getResourceAsStream("climate.lzma"))) { buf = Unpooled.wrappedBuffer(StreamUtil.toByteArray(in)); @@ -27,7 +28,7 @@ public abstract class Climate extends AbstractBuiltinDataset implements IntToDou out[i++] = buf.readFloat(); } return out; - }); + }, ReferenceStrength.WEAK); protected final double[] data = DATA_CACHE.get(); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/Soil.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/Soil.java index 23554530..1d6c43b0 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/Soil.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/builtin/Soil.java @@ -6,7 +6,8 @@ import net.buildtheearth.terraplusplus.util.RLEByteArray; import net.daporkchop.lib.binary.oio.StreamUtil; import net.daporkchop.lib.common.function.io.IOSupplier; -import net.daporkchop.lib.common.ref.Ref; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; import java.io.InputStream; @@ -16,7 +17,7 @@ public class Soil extends AbstractBuiltinDataset { protected static final int COLS = 10800; protected static final int ROWS = 5400; - private static final Ref DATA_CACHE = Ref.soft((IOSupplier) () -> { + private static final Cached DATA_CACHE = Cached.global((IOSupplier) () -> { ByteBuf buf; try (InputStream in = new LzmaInputStream(Climate.class.getResourceAsStream("soil.lzma"))) { buf = Unpooled.wrappedBuffer(StreamUtil.toByteArray(in)); @@ -27,7 +28,7 @@ public class Soil extends AbstractBuiltinDataset { builder.append(buf.getByte(i)); } return builder.build(); - }); + }, ReferenceStrength.WEAK); private final RLEByteArray data = DATA_CACHE.get(); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/AbstractGeoJsonDeserializer.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/AbstractGeoJsonDeserializer.java deleted file mode 100644 index 116cff45..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/AbstractGeoJsonDeserializer.java +++ /dev/null @@ -1,133 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.geojson; - -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.GeometryCollection; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.LineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPoint; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Point; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Polygon; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * @author DaPorkchop_ - */ -@RequiredArgsConstructor -abstract class AbstractGeoJsonDeserializer extends TypeAdapter { - @NonNull - protected final String name; - - @Override - public void write(JsonWriter out, T value) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public T read(JsonReader in) throws IOException { - in.beginObject(); - checkState("type".equals(in.nextName()), "invalid GeoJSON %s: doesn't start with type!", this.name); - - String type = in.nextString(); - T obj = this.read0(type, in); - checkState(obj != null, "unknown GeoJSON %s type: \"%s\"!", this.name, type); - in.endObject(); - return obj; - } - - protected abstract T read0(String type, JsonReader in) throws IOException; - - protected final Geometry readGeometry(String type, JsonReader in) throws IOException { - String fieldName = in.nextName(); - - if ("GeometryCollection".equals(type)) { //special handling for GeometryCollection - checkState("geometries".equals(fieldName), "unexpected field \"%s\" in GeometryCollection object", fieldName); - List geometries = new ArrayList<>(); - in.beginArray(); - while (in.peek() != JsonToken.END_ARRAY) { - geometries.add(this.geometryDeserializer().read(in)); - } - in.endArray(); - return new GeometryCollection(geometries.toArray(new Geometry[0])); - } - - checkState("coordinates".equals(fieldName), "unexpected field \"%s\" in %s object", fieldName, type); - switch (type) { - case "Point": - return this.readPoint(in); - case "MultiPoint": - return new MultiPoint(this.readPoints(in)); - case "LineString": - return this.readLineString(in); - case "MultiLineString": - return new MultiLineString(this.readLineStrings(in)); - case "Polygon": - return this.readPolygon(in); - case "MultiPolygon": - return new MultiPolygon(this.readPolygons(in)); - } - return null; - } - - protected Point readPoint(JsonReader in) throws IOException { - in.beginArray(); - Point point = new Point(in.nextDouble(), in.nextDouble()); - if (in.peek() == JsonToken.NUMBER) { //optional elevation - in.nextDouble(); - } - in.endArray(); - return point; - } - - protected Point[] readPoints(JsonReader in) throws IOException { - List points = new ArrayList<>(); - in.beginArray(); - while (in.peek() != JsonToken.END_ARRAY) { - points.add(this.readPoint(in)); - } - in.endArray(); - return points.toArray(new Point[0]); - } - - protected LineString readLineString(JsonReader in) throws IOException { - return new LineString(this.readPoints(in)); - } - - protected LineString[] readLineStrings(JsonReader in) throws IOException { - List lines = new ArrayList<>(); - in.beginArray(); - while (in.peek() != JsonToken.END_ARRAY) { - lines.add(this.readLineString(in)); - } - in.endArray(); - return lines.toArray(new LineString[0]); - } - - protected Polygon readPolygon(JsonReader in) throws IOException { - LineString[] lines = this.readLineStrings(in); - return new Polygon(lines[0], Arrays.copyOfRange(lines, 1, lines.length)); - } - - protected Polygon[] readPolygons(JsonReader in) throws IOException { - List polygons = new ArrayList<>(); - in.beginArray(); - while (in.peek() != JsonToken.END_ARRAY) { - polygons.add(this.readPolygon(in)); - } - in.endArray(); - return polygons.toArray(new Polygon[0]); - } - - protected abstract GeometryDeserializer geometryDeserializer(); -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeoJson.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeoJson.java deleted file mode 100644 index 137dc40d..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeoJson.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.geojson; - -import lombok.NonNull; -import lombok.experimental.UtilityClass; -import net.buildtheearth.terraplusplus.TerraConstants; - -import java.io.Reader; - -/** - * @author DaPorkchop_ - */ -@UtilityClass -public class GeoJson { - /** - * Parses a single GeoJSON object from the given {@link Reader}. - * - * @param in the {@link Reader} to read from - * @return the parsed GeoJSON object - */ - public static GeoJsonObject parse(@NonNull Reader in) { - return TerraConstants.GSON.fromJson(in, GeoJsonObject.class); - } - - /** - * Parses a single GeoJSON object from the given {@link String}. - * - * @param json the {@link String} containing the JSON text - * @return the parsed GeoJSON object - */ - public static GeoJsonObject parse(@NonNull String json) { - return TerraConstants.GSON.fromJson(json, GeoJsonObject.class); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeoJsonObject.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeoJsonObject.java index 2a25c4c7..1ffe708d 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeoJsonObject.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeoJsonObject.java @@ -1,10 +1,35 @@ package net.buildtheearth.terraplusplus.dataset.geojson; -import com.google.gson.annotations.JsonAdapter; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.GeometryCollection; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.LineString; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPoint; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Point; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Polygon; +import net.buildtheearth.terraplusplus.dataset.geojson.object.Feature; +import net.buildtheearth.terraplusplus.dataset.geojson.object.FeatureCollection; +import net.buildtheearth.terraplusplus.dataset.geojson.object.Reference; /** * @author DaPorkchop_ */ -@JsonAdapter(ObjectDeserializer.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(Feature.class), + @JsonSubTypes.Type(FeatureCollection.class), + @JsonSubTypes.Type(Reference.class), + @JsonSubTypes.Type(GeometryCollection.class), + @JsonSubTypes.Type(LineString.class), + @JsonSubTypes.Type(MultiLineString.class), + @JsonSubTypes.Type(Point.class), + @JsonSubTypes.Type(MultiPoint.class), + @JsonSubTypes.Type(Polygon.class), + @JsonSubTypes.Type(MultiPolygon.class) +}) +@JsonDeserialize public interface GeoJsonObject { } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/Geometry.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/Geometry.java index 3117e609..1680dc66 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/Geometry.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/Geometry.java @@ -20,8 +20,17 @@ package net.buildtheearth.terraplusplus.dataset.geojson; -import com.google.gson.annotations.JsonAdapter; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.GeometryCollection; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.LineString; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPoint; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Point; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Polygon; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.ProjectionFunction; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; @@ -29,7 +38,17 @@ /** * @author DaPorkchop_ */ -@JsonAdapter(GeometryDeserializer.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(GeometryCollection.class), + @JsonSubTypes.Type(LineString.class), + @JsonSubTypes.Type(MultiLineString.class), + @JsonSubTypes.Type(Point.class), + @JsonSubTypes.Type(MultiPoint.class), + @JsonSubTypes.Type(Polygon.class), + @JsonSubTypes.Type(MultiPolygon.class) +}) +@JsonDeserialize public interface Geometry extends GeoJsonObject { Geometry project(@NonNull ProjectionFunction projection) throws OutOfProjectionBoundsException; diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeometryDeserializer.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeometryDeserializer.java deleted file mode 100644 index e7314d8f..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/GeometryDeserializer.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Adapted from The MIT License (MIT) - * - * Copyright (c) 2020-2021 DaPorkchop_ - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software - * is furnished to do so, subject to the following conditions: - * - * Any persons and/or organizations using this software must include the above copyright notice and this permission notice, - * provide sufficient credit to the original authors of the project (IE: DaPorkchop_), as well as provide a link to the original project. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - */ - -package net.buildtheearth.terraplusplus.dataset.geojson; - -import com.google.gson.stream.JsonReader; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -final class GeometryDeserializer extends AbstractGeoJsonDeserializer { - public GeometryDeserializer() { - super("geometry"); - } - - @Override - protected Geometry read0(String type, JsonReader in) throws IOException { - return super.readGeometry(type, in); - } - - @Override - protected GeometryDeserializer geometryDeserializer() { - return this; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/ObjectDeserializer.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/ObjectDeserializer.java deleted file mode 100644 index 22e2f5c2..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/ObjectDeserializer.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Adapted from The MIT License (MIT) - * - * Copyright (c) 2020-2021 DaPorkchop_ - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software - * is furnished to do so, subject to the following conditions: - * - * Any persons and/or organizations using this software must include the above copyright notice and this permission notice, - * provide sufficient credit to the original authors of the project (IE: DaPorkchop_), as well as provide a link to the original project. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - */ - -package net.buildtheearth.terraplusplus.dataset.geojson; - -import com.google.common.collect.ImmutableMap; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Getter; -import net.buildtheearth.terraplusplus.dataset.geojson.object.Feature; -import net.buildtheearth.terraplusplus.dataset.geojson.object.FeatureCollection; -import net.buildtheearth.terraplusplus.dataset.geojson.object.Reference; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * @author DaPorkchop_ - */ -final class ObjectDeserializer extends AbstractGeoJsonDeserializer { - @Getter - private final GeometryDeserializer geometryDeserializer = new GeometryDeserializer(); - - public ObjectDeserializer() { - super("object"); - } - - @Override - protected GeoJsonObject read0(String type, JsonReader in) throws IOException { - switch (type) { - case "Feature": - return this.readFeature(in); - case "FeatureCollection": - return this.readFeatureCollection(in); - case "Reference": - return this.readReference(in); - } - - return super.readGeometry(type, in); - } - - protected Feature readFeature(JsonReader in) throws IOException { - Geometry geometry = null; - Map properties = null; - String id = null; - - while (in.peek() == JsonToken.NAME) { - switch (in.nextName()) { - case "geometry": - geometry = this.geometryDeserializer.read(in); - break; - case "properties": - if (in.peek() != JsonToken.NULL) { - in.beginObject(); - ImmutableMap.Builder builder = ImmutableMap.builder(); - while (in.peek() != JsonToken.END_OBJECT) { - builder.put(in.nextName(), in.nextString()); - } - in.endObject(); - properties = builder.build(); - } else { - in.nextNull(); - } - break; - case "id": - id = in.nextString(); - break; - default: - throw new IllegalArgumentException("invalid field name "); - } - } - - return new Feature(geometry, properties, id); - } - - protected FeatureCollection readFeatureCollection(JsonReader in) throws IOException { - String fieldName = in.nextName(); - checkState("features".equals(fieldName), "unexpected field \"%s\" in FeatureCollection object", fieldName); - - List features = new ArrayList<>(); - in.beginArray(); - while (in.peek() != JsonToken.END_ARRAY) { - in.beginObject(); - - checkState("type".equals(in.nextName()), "invalid FeatureCollection: doesn't start with type!"); - String type = in.nextString(); - checkState("Feature".equals(type), "FeatureCollection contains non-Feature element \"%s\"", type); - features.add(this.readFeature(in)); - in.endObject(); - } - in.endArray(); - - return new FeatureCollection(features.toArray(new Feature[0])); - } - - protected Reference readReference(JsonReader in) throws IOException { - String fieldName = in.nextName(); - checkState("location".equals(fieldName), "unexpected field \"%s\" in Reference object", fieldName); - - return new Reference(in.nextString()); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/AbstractReferenceResolvingGeoJsonDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/AbstractReferenceResolvingGeoJsonDataset.java index c250ffdf..f84be244 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/AbstractReferenceResolvingGeoJsonDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/AbstractReferenceResolvingGeoJsonDataset.java @@ -46,6 +46,10 @@ public AbstractReferenceResolvingGeoJsonDataset( @Override public CompletableFuture load(@NonNull String key) throws Exception { return this.delegate.getAsync(key).thenCompose(objects -> { + if (objects == null) { //404 not found + return CompletableFuture.completedFuture(this.translate(Stream.empty())); + } + if (!areAnyObjectsReferences(objects)) { //none of the objects are references, so there's nothing to be resolved! return CompletableFuture.completedFuture(this.translate(Arrays.stream(objects))); } @@ -55,7 +59,9 @@ public CompletableFuture load(@NonNull String key) throws Exception { List> referenceFutures = new ArrayList<>(); for (GeoJsonObject object : objects) { if (object instanceof Reference) { - referenceFutures.add(this.getAsync(((Reference) object).location())); + String location = ((Reference) object).location(); + String prefix = key.substring(0, key.indexOf('/') + 1); //TODO: this is gross + referenceFutures.add(this.getAsync(prefix + location)); } else { nonReferenceObjects.add(object); } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/ParsingGeoJsonDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/ParsingGeoJsonDataset.java index 36df33c2..59373635 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/ParsingGeoJsonDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/ParsingGeoJsonDataset.java @@ -4,12 +4,14 @@ import io.netty.buffer.ByteBufInputStream; import lombok.NonNull; import net.buildtheearth.terraplusplus.dataset.KeyedHttpDataset; -import net.buildtheearth.terraplusplus.dataset.geojson.GeoJson; import net.buildtheearth.terraplusplus.dataset.geojson.GeoJsonObject; +import net.daporkchop.lib.common.function.io.IOFunction; import java.io.BufferedReader; import java.io.InputStreamReader; +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; + /** * @author DaPorkchop_ */ @@ -21,7 +23,7 @@ public ParsingGeoJsonDataset(@NonNull String[] urls) { @Override protected GeoJsonObject[] decode(@NonNull String path, @NonNull ByteBuf data) throws Exception { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteBufInputStream(data)))) { //parse each line as a GeoJSON object - return reader.lines().map(GeoJson::parse).toArray(GeoJsonObject[]::new); + return reader.lines().map((IOFunction) s -> JSON_MAPPER.readValue(s, GeoJsonObject.class)).toArray(GeoJsonObject[]::new); } } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/TiledGeoJsonDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/TiledGeoJsonDataset.java index 176efeba..1a012d2d 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/TiledGeoJsonDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/dataset/TiledGeoJsonDataset.java @@ -8,6 +8,7 @@ import net.buildtheearth.terraplusplus.projection.EquirectangularProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; import net.minecraft.util.math.ChunkPos; @@ -29,14 +30,14 @@ public TiledGeoJsonDataset(@NonNull IDataset delegate) } @Override - public CompletableFuture load(@NonNull ChunkPos key) throws Exception { - return this.delegate.getAsync(String.format("tile/%d/%d.json", key.x, key.z)); + public CompletableFuture load(@NonNull TilePos key) throws Exception { + return this.delegate.getAsync(String.format("%d/tile/%d/%d.json", key.zoom(), key.x(), key.z())); } @Override - public CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds) throws OutOfProjectionBoundsException { + public CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds, int zoom) throws OutOfProjectionBoundsException { Bounds2d localBounds = bounds.fromGeo(this.projection).axisAlign(); - CompletableFuture[] futures = uncheckedCast(Arrays.stream(localBounds.toTiles(this.tileSize)) + CompletableFuture[] futures = uncheckedCast(Arrays.stream(localBounds.toTiles(this.tileSize, zoom)) .map(this::getAsync) .toArray(CompletableFuture[]::new)); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/GeometryCollection.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/GeometryCollection.java index 478b70d0..a61e674b 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/GeometryCollection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/GeometryCollection.java @@ -1,8 +1,15 @@ package net.buildtheearth.terraplusplus.dataset.geojson.geometry; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.collect.Iterators; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.ProjectionFunction; @@ -14,11 +21,20 @@ /** * @author DaPorkchop_ */ -@Data +@Getter(onMethod_ = { @JsonGetter }) +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("GeometryCollection") public final class GeometryCollection implements Geometry, Iterable { - @NonNull protected final Geometry[] geometries; + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GeometryCollection( + @JsonProperty(value = "geometries", required = true) @NonNull Geometry[] geometries) { + this.geometries = geometries; + } + @Override public Iterator iterator() { return Iterators.forArray(this.geometries); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/LineString.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/LineString.java index a851b30f..90a5ea46 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/LineString.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/LineString.java @@ -1,12 +1,32 @@ package net.buildtheearth.terraplusplus.dataset.geojson.geometry; -import lombok.Data; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.ProjectionFunction; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import static java.lang.Math.*; @@ -15,15 +35,26 @@ /** * @author DaPorkchop_ */ -@Data +@Getter +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("LineString") public final class LineString implements Geometry { + @Getter(onMethod_ = { + @JsonGetter("coordinates"), + @JsonSerialize(using = Point.ArraySerializer.class) + }) protected final Point[] points; - public LineString(@NonNull Point[] points) { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public LineString( + @JsonProperty(value = "coordinates", required = true) @JsonDeserialize(using = Point.ArrayDeserializer.class) @NonNull Point[] points) { checkArg(points.length >= 2, "LineString must contain at least 2 points!"); this.points = points; } + @JsonIgnore public boolean isLinearRing() { return this.points.length >= 4 && Objects.equals(this.points[0], this.points[this.points.length - 1]); } @@ -55,4 +86,36 @@ public Bounds2d bounds() { } return Bounds2d.of(minLon, maxLon, minLat, maxLat); } + + protected static final class ArrayDeserializer extends JsonDeserializer { + public static final ArrayDeserializer INSTANCE = new ArrayDeserializer(); + + @Override + public LineString[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + checkState(p.isExpectedStartArrayToken(), "expected array start"); + + List list = new ArrayList<>(); + + JsonToken token = p.nextToken(); + do { + list.add(new LineString(Point.ArrayDeserializer.INSTANCE.deserialize(p, ctxt))); + } while ((token = p.nextToken()) == JsonToken.START_ARRAY); + checkState(token == JsonToken.END_ARRAY, "expected array end, but found %s", token); + + return list.toArray(new LineString[0]); + } + } + + protected static final class ArraySerializer extends JsonSerializer { + public static final ArraySerializer INSTANCE = new ArraySerializer(); + + @Override + public void serialize(LineString[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeStartArray(); + for (LineString lineString : value) { + Point.ArraySerializer.INSTANCE.serialize(lineString.points(), gen, serializers); + } + gen.writeEndArray(); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiLineString.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiLineString.java index 5df8ada2..2fb33d71 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiLineString.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiLineString.java @@ -1,8 +1,16 @@ package net.buildtheearth.terraplusplus.dataset.geojson.geometry; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.collect.Iterators; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.ProjectionFunction; @@ -14,11 +22,24 @@ /** * @author DaPorkchop_ */ -@Data +@Getter +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("MultiLineString") public final class MultiLineString implements Geometry, Iterable { - @NonNull + @Getter(onMethod_ = { + @JsonGetter("coordinates"), + @JsonSerialize(using = LineString.ArraySerializer.class) + }) protected final LineString[] lines; + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public MultiLineString( + @JsonProperty(value = "coordinates", required = true) @JsonDeserialize(using = LineString.ArrayDeserializer.class) @NonNull LineString[] lines) { + this.lines = lines; + } + @Override public Iterator iterator() { return Iterators.forArray(this.lines); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiPoint.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiPoint.java index 7eef1910..9d5f18c8 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiPoint.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiPoint.java @@ -1,8 +1,16 @@ package net.buildtheearth.terraplusplus.dataset.geojson.geometry; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.collect.Iterators; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.ProjectionFunction; @@ -15,11 +23,24 @@ /** * @author DaPorkchop_ */ -@Data +@Getter +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("MultiPoint") public final class MultiPoint implements Geometry, Iterable { - @NonNull + @Getter(onMethod_ = { + @JsonGetter("coordinates"), + @JsonSerialize(using = Point.ArraySerializer.class) + }) protected final Point[] points; + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public MultiPoint( + @JsonProperty(value = "coordinates", required = true) @JsonDeserialize(using = Point.ArrayDeserializer.class) @NonNull Point[] points) { + this.points = points; + } + @Override public Iterator iterator() { return Iterators.forArray(this.points); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiPolygon.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiPolygon.java index 111ca8ad..526ae517 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiPolygon.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/MultiPolygon.java @@ -1,8 +1,16 @@ package net.buildtheearth.terraplusplus.dataset.geojson.geometry; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.collect.Iterators; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.ProjectionFunction; @@ -14,11 +22,24 @@ /** * @author DaPorkchop_ */ -@Data +@Getter +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("MultiPolygon") public final class MultiPolygon implements Geometry, Iterable { - @NonNull + @Getter(onMethod_ = { + @JsonGetter("coordinates"), + @JsonSerialize(using = Polygon.ArraySerializer.class) + }) protected final Polygon[] polygons; + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public MultiPolygon( + @JsonProperty(value = "coordinates", required = true) @JsonDeserialize(using = Polygon.ArrayDeserializer.class) @NonNull Polygon[] polygons) { + this.polygons = polygons; + } + @Override public Iterator iterator() { return Iterators.forArray(this.polygons); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/Point.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/Point.java index c51a162e..ec1a24dc 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/Point.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/Point.java @@ -1,19 +1,59 @@ package net.buildtheearth.terraplusplus.dataset.geojson.geometry; -import lombok.Data; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.ProjectionFunction; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static net.daporkchop.lib.common.util.PValidation.*; + /** * @author DaPorkchop_ */ -@Data +@RequiredArgsConstructor +@Getter +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("Point") public final class Point implements Geometry { - protected final double lon; - protected final double lat; + protected transient final double lon; + protected transient final double lat; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Point( + @JsonProperty(value = "coordinates", required = true) @NonNull double[] coords) { + checkArg(coords.length == 2 || coords.length == 3, "invalid number of point coordinates: %d", coords.length); + this.lon = coords[0]; + this.lat = coords[1]; + } + + @JsonGetter("coordinates") + private double[] coordinates() { + return new double[]{ this.lon, this.lat }; + } @Override public Point project(@NonNull ProjectionFunction projection) throws OutOfProjectionBoundsException { @@ -25,4 +65,56 @@ public Point project(@NonNull ProjectionFunction projection) throws OutOfProject public Bounds2d bounds() { return Bounds2d.of(this.lon, this.lon, this.lat, this.lat); } + + protected static final class ArrayDeserializer extends JsonDeserializer { + public static final ArrayDeserializer INSTANCE = new ArrayDeserializer(); + + @Override + public Point[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + checkState(p.isExpectedStartArrayToken(), "expected array start"); + + List list = new ArrayList<>(); + + JsonToken token = p.nextToken(); + do { + checkState(token == JsonToken.START_ARRAY, "expected array start, but found %s", token); + token = p.nextToken(); + checkState(token.isNumeric(), "expected number, but found %s", token); + double lon = p.getDoubleValue(); + + token = p.nextToken(); + checkState(token.isNumeric(), "expected number, but found %s", token); + double lat = p.getDoubleValue(); + + token = p.nextToken(); + if (token != JsonToken.END_ARRAY) { //third dimension (discard it) + checkState(token.isNumeric(), "expected number or array end, but found %s", token); + + token = p.nextToken(); + checkState(token == JsonToken.END_ARRAY, "expected array end, but found %s", token); + } + + list.add(new Point(lon, lat)); + } while ((token = p.nextToken()) == JsonToken.START_ARRAY); + checkState(token == JsonToken.END_ARRAY, "expected array end, but found %s", token); + + return list.toArray(new Point[0]); + } + } + + protected static final class ArraySerializer extends JsonSerializer { + public static final ArraySerializer INSTANCE = new ArraySerializer(); + + @Override + public void serialize(Point[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeStartArray(); + for (Point point : value) { + gen.writeStartArray(); + gen.writeNumber(point.lon); + gen.writeNumber(point.lat); + gen.writeEndArray(); + } + gen.writeEndArray(); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/Polygon.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/Polygon.java index fec1865f..8ed46018 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/Polygon.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/geometry/Polygon.java @@ -1,22 +1,62 @@ package net.buildtheearth.terraplusplus.dataset.geojson.geometry; -import lombok.Data; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.projection.ProjectionFunction; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import static net.daporkchop.lib.common.util.PValidation.*; /** * @author DaPorkchop_ */ -@Data +@Getter +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("Polygon") public final class Polygon implements Geometry { protected final LineString outerRing; protected final LineString[] innerRings; + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Polygon( + @JsonProperty(value = "coordinates", required = true) @JsonDeserialize(using = LineString.ArrayDeserializer.class) @NonNull LineString[] rings) { + checkArg(rings.length >= 1, "polygon must contain at least one ring!"); + LineString outerRing = rings[0]; + LineString[] innerRings = Arrays.copyOfRange(rings, 1, rings.length); + + checkArg(outerRing.isLinearRing(), "outerRing is not a linear ring!"); + for (int i = 0; i < innerRings.length; i++) { + checkArg(innerRings[i].isLinearRing(), "innerRings[%d] is not a linear ring!", i); + } + this.outerRing = outerRing; + this.innerRings = innerRings; + } + public Polygon(@NonNull LineString outerRing, @NonNull LineString[] innerRings) { checkArg(outerRing.isLinearRing(), "outerRing is not a linear ring!"); for (int i = 0; i < innerRings.length; i++) { @@ -26,6 +66,15 @@ public Polygon(@NonNull LineString outerRing, @NonNull LineString[] innerRings) this.innerRings = innerRings; } + @JsonGetter("coordinates") + @JsonSerialize(using = LineString.ArraySerializer.class) + private LineString[] coordinates() { + LineString[] merged = new LineString[this.innerRings.length + 1]; + merged[0] = this.outerRing; + System.arraycopy(this.innerRings, 0, merged, 1, this.innerRings.length); + return merged; + } + @Override public Polygon project(@NonNull ProjectionFunction projection) throws OutOfProjectionBoundsException { LineString outerRing = this.outerRing.project(projection); @@ -40,4 +89,32 @@ public Polygon project(@NonNull ProjectionFunction projection) throws OutOfProje public Bounds2d bounds() { return this.outerRing.bounds(); } + + protected static final class ArrayDeserializer extends JsonDeserializer { + @Override + public Polygon[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + checkState(p.isExpectedStartArrayToken(), "expected array start"); + + List list = new ArrayList<>(); + + JsonToken token = p.nextToken(); + do { + list.add(new Polygon(LineString.ArrayDeserializer.INSTANCE.deserialize(p, ctxt))); + } while ((token = p.nextToken()) == JsonToken.START_ARRAY); + checkState(token == JsonToken.END_ARRAY, "expected array end, but found %s", token); + + return list.toArray(new Polygon[0]); + } + } + + protected static final class ArraySerializer extends JsonSerializer { + @Override + public void serialize(Polygon[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeStartArray(); + for (Polygon polygon : value) { + LineString.ArraySerializer.INSTANCE.serialize(polygon.coordinates(), gen, serializers); + } + gen.writeEndArray(); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/Feature.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/Feature.java index 42eb5a95..70e783b5 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/Feature.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/Feature.java @@ -1,7 +1,15 @@ package net.buildtheearth.terraplusplus.dataset.geojson.object; -import lombok.Data; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.GeoJsonObject; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; @@ -10,10 +18,25 @@ /** * @author DaPorkchop_ */ -@Data +@Getter(onMethod_ = { @JsonGetter }) +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("Feature") +@JsonInclude(JsonInclude.Include.NON_NULL) public final class Feature implements GeoJsonObject { @NonNull protected final Geometry geometry; protected final Map properties; protected final String id; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Feature( + @JsonProperty(value = "geometry", required = true) @NonNull Geometry geometry, + @JsonProperty("properties") Map properties, + @JsonProperty("id") String id) { + this.geometry = geometry; + this.properties = properties; + this.id = id; + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/FeatureCollection.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/FeatureCollection.java index 3c39d4a0..802c1354 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/FeatureCollection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/FeatureCollection.java @@ -1,8 +1,15 @@ package net.buildtheearth.terraplusplus.dataset.geojson.object; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.collect.Iterators; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.GeoJsonObject; import java.util.Iterator; @@ -10,11 +17,20 @@ /** * @author DaPorkchop_ */ -@Data +@Getter(onMethod_ = { @JsonGetter }) +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("FeatureCollection") public final class FeatureCollection implements GeoJsonObject, Iterable { - @NonNull protected final Feature[] features; + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public FeatureCollection( + @JsonProperty(value = "features", required = true) @NonNull Feature[] features) { + this.features = features; + } + @Override public Iterator iterator() { return Iterators.forArray(this.features); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/Reference.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/Reference.java index 1b868749..2343dc17 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/Reference.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/geojson/object/Reference.java @@ -1,7 +1,14 @@ package net.buildtheearth.terraplusplus.dataset.geojson.object; -import lombok.Data; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import net.buildtheearth.terraplusplus.dataset.geojson.GeoJsonObject; /** @@ -9,8 +16,17 @@ * * @author DaPorkchop_ */ -@Data +@Getter(onMethod_ = { @JsonGetter }) +@ToString +@EqualsAndHashCode +@JsonDeserialize +@JsonTypeName("Reference") public final class Reference implements GeoJsonObject { - @NonNull protected final String location; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Reference( + @JsonProperty(value = "location", required = true) @NonNull String location) { + this.location = location; + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/BlockStateParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/BlockStateParser.java deleted file mode 100644 index 0834fbdb..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/BlockStateParser.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm; - -import com.google.common.collect.ImmutableMap; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import net.daporkchop.lib.common.function.PFunctions; -import net.minecraft.block.Block; -import net.minecraft.block.properties.IProperty; -import net.minecraft.block.state.IBlockState; -import net.minecraft.util.ResourceLocation; - -import java.io.IOException; -import java.util.Collections; -import java.util.Map; -import java.util.stream.Collectors; - -import static net.daporkchop.lib.common.util.PorkUtil.*; - -/** - * Parses block states. - * - * @author DaPorkchop_ - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class BlockStateParser extends JsonParser { - public static final BlockStateParser INSTANCE = new BlockStateParser(); - - @Override - public IBlockState read(JsonReader in) throws IOException { - ResourceLocation id = null; - Map properties = Collections.emptyMap(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "id": - id = new ResourceLocation(in.nextString()); - break; - case "properties": { - ImmutableMap.Builder builder = ImmutableMap.builder(); - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - builder.put(in.nextName(), in.nextString()); - } - in.endObject(); - properties = builder.build(); - break; - } - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - IBlockState state = Block.REGISTRY.getObject(id).getDefaultState(); - Map> lookup = state.getPropertyKeys().stream().collect(Collectors.toMap(IProperty::getName, PFunctions.identity())); - for (Map.Entry entry : properties.entrySet()) { - IProperty property = lookup.get(entry.getKey()); - state = state.withProperty(property, uncheckedCast(property.parseValue(entry.getValue()).orNull())); - } - return state; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/JsonParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/JsonParser.java deleted file mode 100644 index ac7dbc97..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/JsonParser.java +++ /dev/null @@ -1,102 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm; - -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.daporkchop.lib.common.function.io.IOBiFunction; -import net.daporkchop.lib.common.function.io.IOFunction; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * Base implementation of {@link TypeAdapter} for deserialzing - * - * @author DaPorkchop_ - */ -public abstract class JsonParser extends TypeAdapter { - public static List readList(@NonNull JsonReader in, @NonNull IOFunction elementParser) throws IOException { - List list = new ArrayList<>(); - - in.beginArray(); - while (in.peek() != JsonToken.END_ARRAY) { - list.add(elementParser.applyThrowing(in)); - } - in.endArray(); - - return list; - } - - public static List readTypedList(@NonNull JsonReader in, @NonNull Class type) throws IOException { - List list = new ArrayList<>(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - list.add(TerraConstants.GSON.fromJson(in, type)); - } - in.endObject(); - - return list; - } - - public static List readTypedList(@NonNull JsonReader in, @NonNull IOBiFunction elementParser) throws IOException { - List list = new ArrayList<>(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - list.add(elementParser.applyThrowing(in.nextName(), in)); - } - in.endObject(); - - return list; - } - - @Override - public void write(JsonWriter out, T value) throws IOException { - throw new UnsupportedOperationException(); - } - - /** - * Parses values with named types. - * - * @author DaPorkchop_ - */ - @RequiredArgsConstructor - private static abstract class AbstractTyped extends JsonParser implements IOBiFunction { - @NonNull - protected final String name; - @NonNull - protected final Map> types; - - @Override - public T applyThrowing(String type, JsonReader in) throws IOException { - Class clazz = this.types.get(type); - checkArg(clazz != null, "type \"%s\" is not supported by \"%s\"!", type, this.name); - return TerraConstants.GSON.fromJson(in, clazz); - } - } - - /** - * Parses a single value with a named type. - * - * @author DaPorkchop_ - */ - public static abstract class Typed extends AbstractTyped { - public Typed(String name, Map> types) { - super(name, types); - } - - @Override - public T read(JsonReader in) throws IOException { - return this.applyThrowing(in.nextName(), in); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/OSMMapper.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/OSMMapper.java index 8d7ba28f..c0f6cf94 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/OSMMapper.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/OSMMapper.java @@ -1,19 +1,10 @@ package net.buildtheearth.terraplusplus.dataset.osm; -import com.google.gson.JsonParseException; -import com.google.gson.stream.JsonReader; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.NonNull; -import lombok.SneakyThrows; -import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.buildtheearth.terraplusplus.util.http.Disk; -import net.daporkchop.lib.binary.oio.reader.UTF8FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Collection; import java.util.Map; @@ -22,21 +13,8 @@ * * @author DaPorkchop_ */ +@JsonDeserialize(as = Root.class) @FunctionalInterface public interface OSMMapper { - @SneakyThrows(IOException.class) - static OSMMapper load() { - Path path = Disk.configFile("osm.json5"); - try (JsonReader reader = new JsonReader(Files.exists(path) - ? new UTF8FileReader(path.toString()) - : new InputStreamReader(OSMMapper.class.getResourceAsStream("osm.json5")))) { - try { - return TerraConstants.GSON.fromJson(reader, Root.class); - } catch (Exception e) { - throw new JsonParseException(reader.toString(), e); - } - } - } - Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry); } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/Root.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/Root.java index b4b1c21b..b39431e3 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/Root.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/Root.java @@ -1,12 +1,12 @@ package net.buildtheearth.terraplusplus.dataset.osm; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Getter; import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.dataset.geojson.geometry.LineString; import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; @@ -14,12 +14,11 @@ import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Point; import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Polygon; -import net.buildtheearth.terraplusplus.dataset.osm.mapper.LineMapper; -import net.buildtheearth.terraplusplus.dataset.osm.mapper.PolygonMapper; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapper; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon.PolygonMapper; import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; import net.daporkchop.lib.common.util.PorkUtil; -import java.io.IOException; import java.util.Collection; import java.util.Map; @@ -28,15 +27,21 @@ * * @author DaPorkchop_ */ -@JsonAdapter(Root.Parser.class) -@Getter -@Builder +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +@JsonSerialize final class Root implements OSMMapper { - @NonNull protected final LineMapper line; - @NonNull protected final PolygonMapper polygon; + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Root( + @JsonProperty(value = "line", required = true) @NonNull LineMapper line, + @JsonProperty(value = "polygon", required = true) @NonNull PolygonMapper polygon) { + this.line = line; + this.polygon = polygon; + } + @Override public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { if (projectedGeometry instanceof Point || projectedGeometry instanceof MultiPoint) { //points can't be generated @@ -58,33 +63,4 @@ public Collection apply(String id, @NonNull Map throw new IllegalArgumentException("unsupported geometry type: " + PorkUtil.className(projectedGeometry)); } } - - static final class Parser extends JsonParser { - @Override - public Root read(JsonReader in) throws IOException { - RootBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "line": - in.beginObject(); - builder.line(TerraConstants.GSON.fromJson(in, LineMapper.class)); - in.endObject(); - break; - case "polygon": - in.beginObject(); - builder.polygon(TerraConstants.GSON.fromJson(in, PolygonMapper.class)); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/BiOp.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/BiOp.java deleted file mode 100644 index 0fc3bd6b..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/BiOp.java +++ /dev/null @@ -1,167 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.dvalue; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; - -import java.io.IOException; -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -@RequiredArgsConstructor -abstract class BiOp implements DValue { - @NonNull - protected final DValue first; - @NonNull - protected final DValue second; - - static abstract class Parser extends DValueParser { - @Override - public DValue read(JsonReader in) throws IOException { - in.beginObject(); - DValue first = super.read(in); - DValue second = super.read(in); - in.endObject(); - - return this.construct(first, second); - } - - protected abstract DValue construct(@NonNull DValue first, @NonNull DValue second); - } - - @JsonAdapter(Add.Parser.class) - static final class Add extends BiOp { - public Add(DValue first, DValue second) { - super(first, second); - } - - @Override - public double apply(@NonNull Map tags) { - return this.first.apply(tags) + this.second.apply(tags); - } - - static class Parser extends BiOp.Parser { - @Override - protected DValue construct(@NonNull DValue first, @NonNull DValue second) { - return new Add(first, second); - } - } - } - - @JsonAdapter(Subtract.Parser.class) - static final class Subtract extends BiOp { - public Subtract(DValue first, DValue second) { - super(first, second); - } - - @Override - public double apply(@NonNull Map tags) { - return this.first.apply(tags) - this.second.apply(tags); - } - - static class Parser extends BiOp.Parser { - @Override - protected DValue construct(@NonNull DValue first, @NonNull DValue second) { - return new Subtract(first, second); - } - } - } - - @JsonAdapter(Multiply.Parser.class) - static final class Multiply extends BiOp { - public Multiply(DValue first, DValue second) { - super(first, second); - } - - @Override - public double apply(@NonNull Map tags) { - return this.first.apply(tags) * this.second.apply(tags); - } - - static class Parser extends BiOp.Parser { - @Override - protected DValue construct(@NonNull DValue first, @NonNull DValue second) { - return new Multiply(first, second); - } - } - } - - @JsonAdapter(Divide.Parser.class) - static final class Divide extends BiOp { - public Divide(DValue first, DValue second) { - super(first, second); - } - - @Override - public double apply(@NonNull Map tags) { - return this.first.apply(tags) / this.second.apply(tags); - } - - static class Parser extends BiOp.Parser { - @Override - protected DValue construct(@NonNull DValue first, @NonNull DValue second) { - return new Divide(first, second); - } - } - } - - @JsonAdapter(FloorDiv.Parser.class) - static final class FloorDiv extends BiOp { - public FloorDiv(DValue first, DValue second) { - super(first, second); - } - - @Override - public double apply(@NonNull Map tags) { - return Math.floor(this.first.apply(tags) / this.second.apply(tags)); - } - - static class Parser extends BiOp.Parser { - @Override - protected DValue construct(@NonNull DValue first, @NonNull DValue second) { - return new FloorDiv(first, second); - } - } - } - - @JsonAdapter(Min.Parser.class) - static final class Min extends BiOp { - public Min(DValue first, DValue second) { - super(first, second); - } - - @Override - public double apply(@NonNull Map tags) { - return Math.min(this.first.apply(tags), this.second.apply(tags)); - } - - static class Parser extends BiOp.Parser { - @Override - protected DValue construct(@NonNull DValue first, @NonNull DValue second) { - return new Min(first, second); - } - } - } - - @JsonAdapter(Max.Parser.class) - static final class Max extends BiOp { - public Max(DValue first, DValue second) { - super(first, second); - } - - @Override - public double apply(@NonNull Map tags) { - return Math.max(this.first.apply(tags), this.second.apply(tags)); - } - - static class Parser extends BiOp.Parser { - @Override - protected DValue construct(@NonNull DValue first, @NonNull DValue second) { - return new Max(first, second); - } - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/Constant.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/Constant.java deleted file mode 100644 index 7f9edcaf..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/Constant.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.dvalue; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.io.IOException; -import java.util.Map; - -/** - * Returns a single, constant value. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Constant.Parser.class) -@RequiredArgsConstructor -final class Constant implements DValue { - protected final double value; - - @Override - public double apply(@NonNull Map tags) { - return this.value; - } - - static class Parser extends JsonParser { - @Override - public DValue read(JsonReader in) throws IOException { - return new Constant(in.nextDouble()); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValue.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValue.java index 98d0a81e..43ae9ec9 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValue.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValue.java @@ -1,15 +1,26 @@ package net.buildtheearth.terraplusplus.dataset.osm.dvalue; -import com.google.gson.annotations.JsonAdapter; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import lombok.NonNull; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; import java.util.Map; /** * @author DaPorkchop_ */ -@JsonAdapter(DValueParser.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") +@JsonTypeIdResolver(DValue.TypeIdResolver.class) +@JsonDeserialize @FunctionalInterface public interface DValue { double apply(@NonNull Map tags); + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.OSM_DVALUES); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueBinaryOperator.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueBinaryOperator.java new file mode 100644 index 00000000..b86400fa --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueBinaryOperator.java @@ -0,0 +1,129 @@ +package net.buildtheearth.terraplusplus.dataset.osm.dvalue; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import java.util.Map; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@RequiredArgsConstructor +public abstract class DValueBinaryOperator implements DValue { + @NonNull + protected final DValue first; + @NonNull + protected final DValue second; + + @JsonDeserialize + public static final class Add extends DValueBinaryOperator { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Add( + @JsonProperty(value = "first", required = true) @JsonAlias({"a"}) @NonNull DValue first, + @JsonProperty(value = "second", required = true) @JsonAlias({"b"}) @NonNull DValue second) { + super(first, second); + } + + @Override + public double apply(@NonNull Map tags) { + return this.first.apply(tags) + this.second.apply(tags); + } + } + + @JsonDeserialize + public static final class Subtract extends DValueBinaryOperator { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Subtract( + @JsonProperty(value = "first", required = true) @JsonAlias({"a"}) @NonNull DValue first, + @JsonProperty(value = "second", required = true) @JsonAlias({"b"}) @NonNull DValue second) { + super(first, second); + } + + @Override + public double apply(@NonNull Map tags) { + return this.first.apply(tags) - this.second.apply(tags); + } + } + + @JsonDeserialize + public static final class Multiply extends DValueBinaryOperator { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Multiply( + @JsonProperty(value = "first", required = true) @JsonAlias({"a"}) @NonNull DValue first, + @JsonProperty(value = "second", required = true) @JsonAlias({"b"}) @NonNull DValue second) { + super(first, second); + } + + @Override + public double apply(@NonNull Map tags) { + return this.first.apply(tags) * this.second.apply(tags); + } + } + + @JsonDeserialize + public static final class Divide extends DValueBinaryOperator { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Divide( + @JsonProperty(value = "first", required = true) @JsonAlias({"a"}) @NonNull DValue first, + @JsonProperty(value = "second", required = true) @JsonAlias({"b"}) @NonNull DValue second) { + super(first, second); + } + + @Override + public double apply(@NonNull Map tags) { + return this.first.apply(tags) / this.second.apply(tags); + } + } + + @JsonDeserialize + public static final class FloorDiv extends DValueBinaryOperator { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public FloorDiv( + @JsonProperty(value = "first", required = true) @JsonAlias({"a"}) @NonNull DValue first, + @JsonProperty(value = "second", required = true) @JsonAlias({"b"}) @NonNull DValue second) { + super(first, second); + } + + @Override + public double apply(@NonNull Map tags) { + return Math.floor(this.first.apply(tags) / this.second.apply(tags)); + } + } + + @JsonDeserialize + public static final class Min extends DValueBinaryOperator { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Min( + @JsonProperty(value = "first", required = true) @JsonAlias({"a"}) @NonNull DValue first, + @JsonProperty(value = "second", required = true) @JsonAlias({"b"}) @NonNull DValue second) { + super(first, second); + } + + @Override + public double apply(@NonNull Map tags) { + return Math.min(this.first.apply(tags), this.second.apply(tags)); + } + } + + @JsonDeserialize + public static final class Max extends DValueBinaryOperator { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Max( + @JsonProperty(value = "first", required = true) @JsonAlias({"a"}) @NonNull DValue first, + @JsonProperty(value = "second", required = true) @JsonAlias({"b"}) @NonNull DValue second) { + super(first, second); + } + + @Override + public double apply(@NonNull Map tags) { + return Math.max(this.first.apply(tags), this.second.apply(tags)); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/condition/NotDC.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueConstant.java similarity index 50% rename from src/main/java/net/buildtheearth/terraplusplus/config/condition/NotDC.java rename to src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueConstant.java index ae9925f8..9d23fb9f 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/config/condition/NotDC.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueConstant.java @@ -1,4 +1,4 @@ -package net.buildtheearth.terraplusplus.config.condition; +package net.buildtheearth.terraplusplus.dataset.osm.dvalue; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonGetter; @@ -7,21 +7,26 @@ import lombok.Getter; import lombok.NonNull; +import java.util.Map; + /** + * Returns a single, constant value. + * * @author DaPorkchop_ */ -@JsonDeserialize @Getter(onMethod_ = { @JsonGetter }) -public class NotDC implements DoubleCondition { - protected final DoubleCondition delegate; +@JsonDeserialize +public final class DValueConstant implements DValue { + protected final double value; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public NotDC(@JsonProperty(value = "delegate", required = true) @NonNull DoubleCondition delegate) { - this.delegate = delegate; + public DValueConstant( + @JsonProperty(value = "value", required = true) double value) { + this.value = value; } @Override - public boolean test(double value) { - return !this.delegate.test(value); + public double apply(@NonNull Map tags) { + return this.value; } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueParser.java deleted file mode 100644 index a195525d..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueParser.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.dvalue; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -public class DValueParser extends JsonParser.Typed { - public static final Map> TYPES = new Object2ObjectOpenHashMap<>(); - - static { - TYPES.put("+", BiOp.Add.class); - TYPES.put("-", BiOp.Subtract.class); - TYPES.put("*", BiOp.Multiply.class); - TYPES.put("/", BiOp.Divide.class); - - TYPES.put("constant", Constant.class); - TYPES.put("floor_div", BiOp.FloorDiv.class); - TYPES.put("min", BiOp.Min.class); - TYPES.put("max", BiOp.Max.class); - TYPES.put("tag", Tag.class); - } - - public DValueParser() { - super("dvalue", TYPES); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueTag.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueTag.java new file mode 100644 index 00000000..f88e7692 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/DValueTag.java @@ -0,0 +1,42 @@ +package net.buildtheearth.terraplusplus.dataset.osm.dvalue; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; + +import java.util.Map; + +/** + * Returns a single, constant value. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DValueTag implements DValue { + protected final String key; + protected final double fallback; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DValueTag( + @JsonProperty(value = "key", required = true) @NonNull String key, + @JsonProperty(value = "fallback", required = true) double fallback) { + this.key = key.intern(); + this.fallback = fallback; + } + + @Override + public double apply(@NonNull Map tags) { + String value = tags.get(this.key); + if (value != null) { + try { + return Double.parseDouble(value); + } catch (NumberFormatException ignored) { + } + } + return this.fallback; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/Tag.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/Tag.java deleted file mode 100644 index da452c8e..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/dvalue/Tag.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.dvalue; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.io.IOException; -import java.util.Map; - -/** - * Returns a single, constant value. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Tag.Parser.class) -@Builder -final class Tag implements DValue { - @NonNull - protected final String key; - protected final double fallback; - - @Override - public double apply(@NonNull Map tags) { - String value = tags.get(this.key); - if (value != null) { - try { - return Double.parseDouble(value); - } catch (NumberFormatException ignored) { - } - } - return this.fallback; - } - - static class Parser extends JsonParser { - @Override - public DValue read(JsonReader in) throws IOException { - TagBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "key": - builder.key(in.nextString().intern()); - break; - case "fallback": - builder.fallback(in.nextDouble()); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperAll.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperAll.java new file mode 100644 index 00000000..7786c2fa --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperAll.java @@ -0,0 +1,41 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper; + +import com.fasterxml.jackson.annotation.JsonGetter; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Returns the combined results of all of a number of mappers, or {@code null} if any one of them returns {@code null}. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@AllArgsConstructor +public abstract class AbstractMapperAll> implements OSMMapper { + @NonNull + protected final M[] children; + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { + List out = new ArrayList<>(); + int i = 0; + for (M child : this.children) { + Collection result = child.apply(id + '/' + i++, tags, originalGeometry, projectedGeometry); + if (result == null) { //don't bother processing further children + return null; + } + out.addAll(result); + } + + return out; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperAny.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperAny.java new file mode 100644 index 00000000..971bba21 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperAny.java @@ -0,0 +1,40 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper; + +import com.fasterxml.jackson.annotation.JsonGetter; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Returns the combined results of all of a number of mappers, ignoring any one of them that returns {@code null}. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@AllArgsConstructor +public abstract class AbstractMapperAny> implements OSMMapper { + @NonNull + protected final M[] children; + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { + List out = new ArrayList<>(); + int i = 0; + for (M child : this.children) { + Collection result = child.apply(id + '/' + i++, tags, originalGeometry, projectedGeometry); + if (result != null) { //don't bother processing further children + out.addAll(result); + } + } + + return out; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperCondition.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperCondition.java new file mode 100644 index 00000000..bc044c78 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperCondition.java @@ -0,0 +1,36 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper; + +import com.fasterxml.jackson.annotation.JsonGetter; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchCondition; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; + +import java.util.Collection; +import java.util.Map; + +/** + * Forwards elements to another mapper if a given {@link MatchCondition} matches. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@AllArgsConstructor +public abstract class AbstractMapperCondition> implements OSMMapper { + @NonNull + protected final MatchCondition match; + @NonNull + protected final M emit; + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { + if (!this.match.test(id, tags, originalGeometry, projectedGeometry)) { //element doesn't match, emit nothing + return null; + } + + return this.emit.apply(id, tags, originalGeometry, projectedGeometry); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperFirst.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperFirst.java new file mode 100644 index 00000000..00d9f6db --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperFirst.java @@ -0,0 +1,36 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper; + +import com.fasterxml.jackson.annotation.JsonGetter; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; + +import java.util.Collection; +import java.util.Map; + +/** + * Returns the result of the first of a number of mappers that returned a non-{@code null} value. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@AllArgsConstructor +public abstract class AbstractMapperFirst> implements OSMMapper { + @NonNull + protected final M[] children; + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { + for (M child : this.children) { + Collection result = child.apply(id, tags, originalGeometry, projectedGeometry); + if (result != null) { + return result; + } + } + + return null; //none matched! + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperNothing.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperNothing.java new file mode 100644 index 00000000..2e1ec5b4 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/AbstractMapperNothing.java @@ -0,0 +1,22 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper; + +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * Returns a non-null, empty list. + * + * @author DaPorkchop_ + */ +public abstract class AbstractMapperNothing> implements OSMMapper { + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/All.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/All.java deleted file mode 100644 index 7cbc7f55..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/All.java +++ /dev/null @@ -1,88 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.AllArgsConstructor; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.daporkchop.lib.common.util.GenericMatcher; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * Returns the combined results of all of a number of mappers, or {@code null} if any one of them returns {@code null}. - * - * @author DaPorkchop_ - */ -@AllArgsConstructor -abstract class All> implements OSMMapper { - @NonNull - protected final M[] children; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { - List out = new ArrayList<>(); - int i = 0; - for (M child : this.children) { - Collection result = child.apply(id + '/' + i++, tags, originalGeometry, projectedGeometry); - if (result == null) { //don't bother processing further children - return null; - } - out.addAll(result); - } - - return out; - } - - static abstract class Parser> extends JsonParser { - protected final Class mapperClass = GenericMatcher.uncheckedFind(this.getClass(), Parser.class, "M"); - - @Override - public M read(JsonReader in) throws IOException { - List children = readTypedList(in, this.mapperClass); - checkState(!children.isEmpty(), "at least one member required!"); - return this.construct(children); - } - - protected abstract M construct(@NonNull List children); - } - - @JsonAdapter(Line.Parser.class) - static class Line extends All implements LineMapper { - public Line(LineMapper[] children) { - super(children); - } - - static class Parser extends All.Parser { - @Override - protected LineMapper construct(@NonNull List children) { - return children.size() == 1 ? children.get(0) : new Line(children.toArray(new LineMapper[0])); - } - } - } - - @JsonAdapter(Polygon.Parser.class) - static class Polygon extends All implements PolygonMapper { - public Polygon(PolygonMapper[] children) { - super(children); - } - - static class Parser extends All.Parser { - @Override - protected PolygonMapper construct(@NonNull List children) { - return children.size() == 1 ? children.get(0) : new Polygon(children.toArray(new PolygonMapper[0])); - } - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Any.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Any.java deleted file mode 100644 index 8562d168..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Any.java +++ /dev/null @@ -1,87 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.AllArgsConstructor; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.daporkchop.lib.common.util.GenericMatcher; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * Returns the combined results of all of a number of mappers, ignoring any one of them that returns {@code null}. - * - * @author DaPorkchop_ - */ -@AllArgsConstructor -abstract class Any> implements OSMMapper { - @NonNull - protected final M[] children; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { - List out = new ArrayList<>(); - int i = 0; - for (M child : this.children) { - Collection result = child.apply(id + '/' + i++, tags, originalGeometry, projectedGeometry); - if (result != null) { //don't bother processing further children - out.addAll(result); - } - } - - return out; - } - - static abstract class Parser> extends JsonParser { - protected final Class mapperClass = GenericMatcher.uncheckedFind(this.getClass(), Parser.class, "M"); - - @Override - public M read(JsonReader in) throws IOException { - List children = readTypedList(in, this.mapperClass); - checkState(!children.isEmpty(), "at least one member required!"); - return this.construct(children); - } - - protected abstract M construct(@NonNull List children); - } - - @JsonAdapter(Line.Parser.class) - static class Line extends Any implements LineMapper { - public Line(LineMapper[] children) { - super(children); - } - - static class Parser extends Any.Parser { - @Override - protected LineMapper construct(@NonNull List children) { - return children.size() == 1 ? children.get(0) : new Line(children.toArray(new LineMapper[0])); - } - } - } - - @JsonAdapter(Polygon.Parser.class) - static class Polygon extends Any implements PolygonMapper { - public Polygon(PolygonMapper[] children) { - super(children); - } - - static class Parser extends Any.Parser { - @Override - protected PolygonMapper construct(@NonNull List children) { - return children.size() == 1 ? children.get(0) : new Polygon(children.toArray(new PolygonMapper[0])); - } - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Condition.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Condition.java deleted file mode 100644 index e020bbec..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Condition.java +++ /dev/null @@ -1,103 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.AllArgsConstructor; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; -import net.buildtheearth.terraplusplus.dataset.osm.match.MatchCondition; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.daporkchop.lib.common.util.GenericMatcher; - -import java.io.IOException; -import java.util.Collection; -import java.util.Map; - -/** - * Forwards elements to another mapper if a given {@link MatchCondition} matches. - * - * @author DaPorkchop_ - */ -@AllArgsConstructor -abstract class Condition> implements OSMMapper { - @NonNull - protected final MatchCondition match; - @NonNull - protected final M emit; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { - if (!this.match.test(id, tags, originalGeometry, projectedGeometry)) { //element doesn't match, emit nothing - return null; - } - - return this.emit.apply(id, tags, originalGeometry, projectedGeometry); - } - - static abstract class Parser, I extends Condition> extends JsonParser { - protected final Class mapperClass = GenericMatcher.uncheckedFind(this.getClass(), Parser.class, "M"); - - @Override - public I read(JsonReader in) throws IOException { - MatchCondition match = null; - M emit = null; - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "match": - in.beginObject(); - match = TerraConstants.GSON.fromJson(in, MatchCondition.class); - in.endObject(); - break; - case "emit": - in.beginObject(); - emit = TerraConstants.GSON.fromJson(in, this.mapperClass); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - return this.construct(match, emit); - } - - protected abstract I construct(@NonNull MatchCondition match, @NonNull M emit); - } - - @JsonAdapter(Line.Parser.class) - static class Line extends Condition implements LineMapper { - public Line(MatchCondition match, LineMapper emit) { - super(match, emit); - } - - static class Parser extends Condition.Parser { - @Override - protected Line construct(@NonNull MatchCondition match, @NonNull LineMapper emit) { - return new Line(match, emit); - } - } - } - - @JsonAdapter(Polygon.Parser.class) - static class Polygon extends Condition implements PolygonMapper { - public Polygon(MatchCondition match, PolygonMapper emit) { - super(match, emit); - } - - static class Parser extends Condition.Parser { - @Override - protected Polygon construct(@NonNull MatchCondition match, @NonNull PolygonMapper emit) { - return new Polygon(match, emit); - } - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/First.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/First.java deleted file mode 100644 index 38256977..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/First.java +++ /dev/null @@ -1,84 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.AllArgsConstructor; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.daporkchop.lib.common.util.GenericMatcher; - -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static net.daporkchop.lib.common.util.PValidation.*; - -/** - * Returns the result of the first of a number of mappers that returned a non-{@code null} value. - * - * @author DaPorkchop_ - */ -@AllArgsConstructor -abstract class First> implements OSMMapper { - @NonNull - protected final M[] children; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { - for (M child : this.children) { - Collection result = child.apply(id, tags, originalGeometry, projectedGeometry); - if (result != null) { - return result; - } - } - - return null; //none matched! - } - - static abstract class Parser> extends JsonParser { - protected final Class mapperClass = GenericMatcher.uncheckedFind(this.getClass(), Parser.class, "M"); - - @Override - public M read(JsonReader in) throws IOException { - List children = readTypedList(in, this.mapperClass); - checkState(!children.isEmpty(), "at least one member required!"); - return this.construct(children); - } - - protected abstract M construct(@NonNull List children); - } - - @JsonAdapter(Line.Parser.class) - static class Line extends First implements LineMapper { - public Line(LineMapper[] children) { - super(children); - } - - static class Parser extends First.Parser { - @Override - protected LineMapper construct(@NonNull List children) { - return children.size() == 1 ? children.get(0) : new Line(children.toArray(new LineMapper[0])); - } - } - } - - @JsonAdapter(Polygon.Parser.class) - static class Polygon extends First implements PolygonMapper { - public Polygon(PolygonMapper[] children) { - super(children); - } - - static class Parser extends First.Parser { - @Override - protected PolygonMapper construct(@NonNull List children) { - return children.size() == 1 ? children.get(0) : new Polygon(children.toArray(new PolygonMapper[0])); - } - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineMapper.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineMapper.java deleted file mode 100644 index f0b1cee9..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineMapper.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(LineParser.class) -@FunctionalInterface -public interface LineMapper extends OSMMapper { -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineNarrow.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineNarrow.java deleted file mode 100644 index 005309ab..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineNarrow.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; -import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.line.NarrowLine; - -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(LineNarrow.Parser.class) -@Builder -final class LineNarrow implements LineMapper { - @NonNull - protected final DrawFunction draw; - @NonNull - protected final DValue layer; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiLineString projectedGeometry) { - return Collections.singletonList(new NarrowLine(id, this.layer.apply(tags), this.draw, projectedGeometry)); - } - - static final class Parser extends JsonParser { - @Override - public LineNarrow read(JsonReader in) throws IOException { - LineNarrowBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "draw": - in.beginObject(); - builder.draw(TerraConstants.GSON.fromJson(in, DrawFunction.class)); - in.endObject(); - break; - case "layer": - in.beginObject(); - builder.layer(TerraConstants.GSON.fromJson(in, DValue.class)); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineParser.java deleted file mode 100644 index e08a04f5..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineParser.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -public class LineParser extends JsonParser.Typed { - public static final Map> TYPES = new Object2ObjectOpenHashMap<>(); - - static { - TYPES.put("all", All.Line.class); - TYPES.put("any", Any.Line.class); - TYPES.put("condition", Condition.Line.class); - TYPES.put("first", First.Line.class); - TYPES.put("nothing", Nothing.Line.class); - - TYPES.put("narrow", LineNarrow.class); - TYPES.put("wide", LineWide.class); - } - - public LineParser() { - super("line", TYPES); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineWide.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineWide.java deleted file mode 100644 index 879335c3..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/LineWide.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; -import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.line.WideLine; - -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(LineWide.Parser.class) -@Builder -final class LineWide implements LineMapper { - @NonNull - protected final DrawFunction draw; - @NonNull - protected final DValue layer; - @NonNull - protected final DValue radius; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiLineString projectedGeometry) { - return Collections.singleton(new WideLine(id, this.layer.apply(tags), this.draw, projectedGeometry, this.radius.apply(tags))); - } - - static final class Parser extends JsonParser { - @Override - public LineWide read(JsonReader in) throws IOException { - LineWideBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "draw": - in.beginObject(); - builder.draw(TerraConstants.GSON.fromJson(in, DrawFunction.class)); - in.endObject(); - break; - case "layer": - in.beginObject(); - builder.layer(TerraConstants.GSON.fromJson(in, DValue.class)); - in.endObject(); - break; - case "radius": - in.beginObject(); - builder.radius(TerraConstants.GSON.fromJson(in, DValue.class)); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Nothing.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Nothing.java deleted file mode 100644 index b03eb560..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/Nothing.java +++ /dev/null @@ -1,59 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; - -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -/** - * Returns a non-null, empty list. - * - * @author DaPorkchop_ - */ -abstract class Nothing> implements OSMMapper { - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull G projectedGeometry) { - return Collections.emptyList(); - } - - static abstract class Parser> extends JsonParser { - @Override - public M read(JsonReader in) throws IOException { - in.beginObject(); - in.endObject(); - return this.construct(); - } - - protected abstract M construct(); - } - - @JsonAdapter(Line.Parser.class) - static class Line extends Nothing implements LineMapper { - static class Parser extends Nothing.Parser { - @Override - protected LineMapper construct() { - return new Line(); - } - } - } - - @JsonAdapter(Polygon.Parser.class) - static class Polygon extends Nothing implements PolygonMapper { - static class Parser extends Nothing.Parser { - @Override - protected PolygonMapper construct() { - return new Polygon(); - } - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonConvert.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonConvert.java deleted file mode 100644 index 1fc5ed5b..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonConvert.java +++ /dev/null @@ -1,77 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.LineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Polygon; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(PolygonConvert.Parser.class) -public interface PolygonConvert extends PolygonMapper { - class Parser extends JsonParser.Typed { - public static final Map> TYPES = new Object2ObjectOpenHashMap<>(); - - static { - TYPES.put("line", Line.class); - } - - public Parser() { - super("convert", TYPES); - } - - @Override - public PolygonConvert read(JsonReader in) throws IOException { - in.beginObject(); - PolygonConvert result = super.read(in); - in.endObject(); - return result; - } - } - - @JsonAdapter(Line.Parser.class) - @RequiredArgsConstructor - final class Line implements PolygonConvert { - @NonNull - protected final LineMapper next; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiPolygon projectedGeometry) { - //convert multipolygon to multilinestring - List lines = new ArrayList<>(); - for (Polygon polygon : projectedGeometry.polygons()) { - lines.add(polygon.outerRing()); - lines.addAll(Arrays.asList(polygon.innerRings())); - } - - return this.next.apply(id, tags, originalGeometry, new MultiLineString(lines.toArray(new LineString[0]))); - } - - static class Parser extends JsonParser { - @Override - public Line read(JsonReader in) throws IOException { - in.beginObject(); - LineMapper next = TerraConstants.GSON.fromJson(in, LineMapper.class); - in.endObject(); - return new Line(next); - } - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonDistance.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonDistance.java deleted file mode 100644 index 69224388..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonDistance.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; -import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.polygon.DistancePolygon; - -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(PolygonDistance.Parser.class) -@Builder -final class PolygonDistance implements PolygonMapper { - @NonNull - protected final DrawFunction draw; - @NonNull - protected final DValue layer; - @Builder.Default - protected final int maxDist = 2; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiPolygon projectedGeometry) { - return Collections.singletonList(new DistancePolygon(id, this.layer.apply(tags), this.draw, projectedGeometry, this.maxDist)); - } - - static final class Parser extends JsonParser { - @Override - public PolygonDistance read(JsonReader in) throws IOException { - PolygonDistanceBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "draw": - in.beginObject(); - builder.draw(TerraConstants.GSON.fromJson(in, DrawFunction.class)); - in.endObject(); - break; - case "layer": - in.beginObject(); - builder.layer(TerraConstants.GSON.fromJson(in, DValue.class)); - in.endObject(); - break; - case "maxDist": - builder.maxDist(in.nextInt()); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonFill.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonFill.java deleted file mode 100644 index de8b245c..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonFill.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; -import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; -import net.buildtheearth.terraplusplus.dataset.vector.geometry.polygon.FillPolygon; - -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(PolygonFill.Parser.class) -@Builder -final class PolygonFill implements PolygonMapper { - @NonNull - protected final DrawFunction draw; - @NonNull - protected final DValue layer; - - @Override - public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiPolygon projectedGeometry) { - return Collections.singletonList(new FillPolygon(id, this.layer.apply(tags), this.draw, projectedGeometry)); - } - - static final class Parser extends JsonParser { - @Override - public PolygonFill read(JsonReader in) throws IOException { - PolygonFillBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "draw": - in.beginObject(); - builder.draw(TerraConstants.GSON.fromJson(in, DrawFunction.class)); - in.endObject(); - break; - case "layer": - in.beginObject(); - builder.layer(TerraConstants.GSON.fromJson(in, DValue.class)); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonMapper.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonMapper.java deleted file mode 100644 index e0f3e71e..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonMapper.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import com.google.gson.annotations.JsonAdapter; -import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; -import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(PolygonParser.class) -@FunctionalInterface -public interface PolygonMapper extends OSMMapper { -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonParser.java deleted file mode 100644 index a0b1989a..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/PolygonParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.mapper; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -public class PolygonParser extends JsonParser.Typed { - public static final Map> TYPES = new Object2ObjectOpenHashMap<>(); - - static { - TYPES.put("all", All.Polygon.class); - TYPES.put("any", Any.Polygon.class); - TYPES.put("condition", Condition.Polygon.class); - TYPES.put("convert", PolygonConvert.class); - TYPES.put("first", First.Polygon.class); - TYPES.put("nothing", Nothing.Polygon.class); - - TYPES.put("distance", PolygonDistance.class); - TYPES.put("fill", PolygonFill.class); - } - - public PolygonParser() { - super("polygon", TYPES); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapper.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapper.java new file mode 100644 index 00000000..1ae0f983 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapper.java @@ -0,0 +1,23 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.line; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; + +/** + * @author DaPorkchop_ + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") +@JsonTypeIdResolver(LineMapper.TypeIdResolver.class) +@JsonDeserialize +@FunctionalInterface +public interface LineMapper extends OSMMapper { + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.OSM_LINE_MAPPERS); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperAll.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperAll.java new file mode 100644 index 00000000..0ed96216 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperAll.java @@ -0,0 +1,20 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.line; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperAll; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class LineMapperAll extends AbstractMapperAll implements LineMapper { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public LineMapperAll( + @JsonProperty(value = "children", required = true) @NonNull LineMapper[] children) { + super(children); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperAny.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperAny.java new file mode 100644 index 00000000..b6052af4 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperAny.java @@ -0,0 +1,20 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.line; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperAny; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class LineMapperAny extends AbstractMapperAny implements LineMapper { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public LineMapperAny( + @JsonProperty(value = "children", required = true) @NonNull LineMapper[] children) { + super(children); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperCondition.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperCondition.java new file mode 100644 index 00000000..4597e7a0 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperCondition.java @@ -0,0 +1,23 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.line; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperCondition; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchCondition; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class LineMapperCondition extends AbstractMapperCondition implements LineMapper { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public LineMapperCondition( + @JsonProperty(value = "match", required = true) @JsonAlias({ "if" }) @NonNull MatchCondition match, + @JsonProperty(value = "emit", required = true) @JsonAlias({ "then" }) @NonNull LineMapper emit) { + super(match, emit); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperFirst.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperFirst.java new file mode 100644 index 00000000..11f0bb30 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperFirst.java @@ -0,0 +1,20 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.line; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperFirst; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class LineMapperFirst extends AbstractMapperFirst implements LineMapper { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public LineMapperFirst( + @JsonProperty(value = "children", required = true) @NonNull LineMapper[] children) { + super(children); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperNarrow.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperNarrow.java new file mode 100644 index 00000000..9517db92 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperNarrow.java @@ -0,0 +1,46 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.line; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.line.NarrowLine; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class LineMapperNarrow implements LineMapper { + protected final DrawFunction draw; + protected final DValue layer; + protected final IntRange levels; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public LineMapperNarrow( + @JsonProperty(value = "draw", required = true) @NonNull DrawFunction draw, + @JsonProperty(value = "layer", required = true) @NonNull DValue layer, + @JsonProperty("levels") @JsonAlias("level") IntRange levels) { + this.draw = draw; + this.layer = layer; + this.levels = levels; + } + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiLineString projectedGeometry) { + return Collections.singletonList(new NarrowLine(id, this.layer.apply(tags), this.draw, this.levels, projectedGeometry, 1)); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperNothing.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperNothing.java new file mode 100644 index 00000000..695fe01b --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperNothing.java @@ -0,0 +1,14 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.line; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperNothing; + +/** + * A {@link LineMapper} which emits nothing. + * + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class LineMapperNothing extends AbstractMapperNothing implements LineMapper { +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperWide.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperWide.java new file mode 100644 index 00000000..67424f09 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/line/LineMapperWide.java @@ -0,0 +1,49 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.line; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.line.WideLine; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class LineMapperWide implements LineMapper { + protected final DrawFunction draw; + protected final DValue layer; + protected final IntRange levels; + protected final DValue radius; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public LineMapperWide( + @JsonProperty(value = "draw", required = true) @NonNull DrawFunction draw, + @JsonProperty(value = "layer", required = true) @NonNull DValue layer, + @JsonProperty("levels") @JsonAlias("level") IntRange levels, + @JsonProperty(value = "radius", required = true) @NonNull DValue radius) { + this.draw = draw; + this.layer = layer; + this.levels = levels; + this.radius = radius; + } + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiLineString projectedGeometry) { + return Collections.singleton(new WideLine(id, this.layer.apply(tags), this.draw, this.levels, projectedGeometry, this.radius.apply(tags))); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapper.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapper.java new file mode 100644 index 00000000..17cb3bf6 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapper.java @@ -0,0 +1,23 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; + +/** + * @author DaPorkchop_ + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") +@JsonTypeIdResolver(PolygonMapper.TypeIdResolver.class) +@JsonDeserialize +@FunctionalInterface +public interface PolygonMapper extends OSMMapper { + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.OSM_POLYGON_MAPPERS); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperAll.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperAll.java new file mode 100644 index 00000000..b3089fdc --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperAll.java @@ -0,0 +1,20 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperAll; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class PolygonMapperAll extends AbstractMapperAll implements PolygonMapper { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public PolygonMapperAll( + @JsonProperty(value = "children", required = true) @NonNull PolygonMapper[] children) { + super(children); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperAny.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperAny.java new file mode 100644 index 00000000..df74fea4 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperAny.java @@ -0,0 +1,20 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperAny; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class PolygonMapperAny extends AbstractMapperAny implements PolygonMapper { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public PolygonMapperAny( + @JsonProperty(value = "children", required = true) @NonNull PolygonMapper[] children) { + super(children); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperCondition.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperCondition.java new file mode 100644 index 00000000..8a0a1e68 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperCondition.java @@ -0,0 +1,23 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperCondition; +import net.buildtheearth.terraplusplus.dataset.osm.match.MatchCondition; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class PolygonMapperCondition extends AbstractMapperCondition implements PolygonMapper { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public PolygonMapperCondition( + @JsonProperty(value = "match", required = true) @JsonAlias({ "if" }) @NonNull MatchCondition match, + @JsonProperty(value = "emit", required = true) @JsonAlias({ "then" }) @NonNull PolygonMapper emit) { + super(match, emit); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperConvertToLines.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperConvertToLines.java new file mode 100644 index 00000000..da7846f8 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperConvertToLines.java @@ -0,0 +1,48 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.LineString; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiLineString; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Polygon; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.line.LineMapper; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class PolygonMapperConvertToLines implements PolygonMapper { + protected final LineMapper next; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public PolygonMapperConvertToLines( + @JsonProperty(value = "next", required = true) @NonNull LineMapper next) { + this.next = next; + } + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiPolygon projectedGeometry) { + //convert multipolygon to multilinestring + List lines = new ArrayList<>(); + for (Polygon polygon : projectedGeometry.polygons()) { + lines.add(polygon.outerRing()); + lines.addAll(Arrays.asList(polygon.innerRings())); + } + + return this.next.apply(id, tags, originalGeometry, new MultiLineString(lines.toArray(new LineString[0]))); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperDistance.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperDistance.java new file mode 100644 index 00000000..62e0313c --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperDistance.java @@ -0,0 +1,51 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.polygon.DistancePolygon; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import static net.daporkchop.lib.common.util.PorkUtil.*; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class PolygonMapperDistance implements PolygonMapper { + protected final DrawFunction draw; + protected final DValue layer; + protected final IntRange levels; + protected final int maxDist; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public PolygonMapperDistance( + @JsonProperty(value = "draw", required = true) @NonNull DrawFunction draw, + @JsonProperty(value = "layer", required = true) @NonNull DValue layer, + @JsonProperty("levels") @JsonAlias("level") IntRange levels, + @JsonProperty("maxDist") Integer maxDist) { + this.draw = draw; + this.layer = layer; + this.levels = levels; + this.maxDist = fallbackIfNull(maxDist, 2); + } + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiPolygon projectedGeometry) { + return Collections.singletonList(new DistancePolygon(id, this.layer.apply(tags), this.draw, this.levels, projectedGeometry, this.maxDist)); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperFill.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperFill.java new file mode 100644 index 00000000..944c0843 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperFill.java @@ -0,0 +1,46 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.osm.dvalue.DValue; +import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; +import net.buildtheearth.terraplusplus.dataset.vector.geometry.polygon.FillPolygon; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class PolygonMapperFill implements PolygonMapper { + protected final DrawFunction draw; + protected final DValue layer; + protected final IntRange levels; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public PolygonMapperFill( + @JsonProperty(value = "draw", required = true) @NonNull DrawFunction draw, + @JsonProperty(value = "layer", required = true) @NonNull DValue layer, + @JsonProperty("levels") @JsonAlias("level") IntRange levels) { + this.draw = draw; + this.layer = layer; + this.levels = levels; + } + + @Override + public Collection apply(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull MultiPolygon projectedGeometry) { + return Collections.singletonList(new FillPolygon(id, this.layer.apply(tags), this.draw, this.levels, projectedGeometry)); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperFirst.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperFirst.java new file mode 100644 index 00000000..c36871b4 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperFirst.java @@ -0,0 +1,20 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperFirst; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class PolygonMapperFirst extends AbstractMapperFirst implements PolygonMapper { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public PolygonMapperFirst( + @JsonProperty(value = "children", required = true) @NonNull PolygonMapper[] children) { + super(children); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperNothing.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperNothing.java new file mode 100644 index 00000000..fd46399b --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/mapper/polygon/PolygonMapperNothing.java @@ -0,0 +1,14 @@ +package net.buildtheearth.terraplusplus.dataset.osm.mapper.polygon; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; +import net.buildtheearth.terraplusplus.dataset.osm.mapper.AbstractMapperNothing; + +/** + * A {@link PolygonMapper} which emits nothing. + * + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class PolygonMapperNothing extends AbstractMapperNothing implements PolygonMapper { +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/And.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/And.java deleted file mode 100644 index dd13404f..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/And.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.match; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; - -import java.io.IOException; -import java.util.Map; - -/** - * Combines the results of multiple match conditions using a logical AND operation. - * - * @author DaPorkchop_ - */ -@JsonAdapter(And.Parser.class) -@RequiredArgsConstructor -final class And implements MatchCondition { - @NonNull - protected final MatchCondition[] delegates; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - for (MatchCondition delegate : this.delegates) { - if (!delegate.test(id, tags, originalGeometry, projectedGeometry)) { - return false; - } - } - return true; - } - - static class Parser extends MatchParser { - @Override - public MatchCondition read(JsonReader in) throws IOException { - return new And(readTypedList(in, this).toArray(new MatchCondition[0])); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Id.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Id.java deleted file mode 100644 index ad5d7085..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Id.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.match; - -import com.google.common.collect.ImmutableSet; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.io.IOException; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -/** - * Matches values based on their OpenStreetMap ID. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Id.Parser.class) -@FunctionalInterface -interface Id extends MatchCondition { - /** - * Matches any ID contained in a {@link Set}. - * - * @author DaPorkchop_ - */ - @RequiredArgsConstructor - final class Any implements Id { - @NonNull - protected final Set expectedIds; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - return this.expectedIds.contains(id); - } - } - - /** - * Matches a single ID. - * - * @author DaPorkchop_ - */ - @RequiredArgsConstructor - final class Exactly implements Id { - protected final String expectedId; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - return Objects.equals(this.expectedId, id); - } - } - - class Parser extends JsonParser { - @Override - public MatchCondition read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.BEGIN_ARRAY) { //check if key is set to any one of the given values - Set expectedIds = ImmutableSet.copyOf(readList(in, reader -> reader.nextString().intern())); - if (expectedIds.isEmpty()) { //if no IDs are given, no ID can possibly match - return FALSE; - } else if (expectedIds.size() == 1) { //if only a single ID is given there's no need to compare against a set - return new Exactly(expectedIds.iterator().next()); - } else { - return new Any(expectedIds); - } - } else { //check if key is set to exactly the given value - return new Exactly(in.nextString().intern()); - } - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Intersects.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Intersects.java deleted file mode 100644 index 5c10bd75..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Intersects.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.match; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.Getter; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; - -import java.io.IOException; -import java.util.Map; - -/** - * Combines the results of multiple match conditions using a logical AND operation. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Intersects.Parser.class) -@Getter -@Builder -final class Intersects implements MatchCondition, Bounds2d { - protected final double minX; - protected final double maxX; - protected final double minZ; - protected final double maxZ; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - Bounds2d bounds = originalGeometry.bounds(); - return bounds != null && this.intersects(bounds); - } - - static class Parser extends JsonParser { - @Override - public Intersects read(JsonReader in) throws IOException { - IntersectsBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "minX": - builder.minX(in.nextDouble()); - break; - case "maxX": - builder.maxX(in.nextDouble()); - break; - case "minZ": - builder.minZ(in.nextDouble()); - break; - case "maxZ": - builder.maxZ(in.nextDouble()); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchCondition.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchCondition.java index 72c28bce..0f504081 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchCondition.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchCondition.java @@ -1,7 +1,10 @@ package net.buildtheearth.terraplusplus.dataset.osm.match; -import com.google.gson.annotations.JsonAdapter; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import lombok.NonNull; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import java.util.Map; @@ -9,7 +12,9 @@ /** * @author DaPorkchop_ */ -@JsonAdapter(MatchParser.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") +@JsonTypeIdResolver(MatchCondition.TypeIdResolver.class) +@JsonDeserialize @FunctionalInterface public interface MatchCondition { /** @@ -18,4 +23,10 @@ public interface MatchCondition { MatchCondition FALSE = (id, tags, originalGeometry, projectedGeometry) -> false; boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry); + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.OSM_MATCH_CONDITIONS); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionAnd.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionAnd.java new file mode 100644 index 00000000..d19eea1f --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionAnd.java @@ -0,0 +1,38 @@ +package net.buildtheearth.terraplusplus.dataset.osm.match; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; + +import java.util.Map; + +/** + * Combines the results of multiple match conditions using a logical AND operation. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class MatchConditionAnd implements MatchCondition { + protected final MatchCondition[] children; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public MatchConditionAnd( + @JsonProperty(value = "children", required = true) @NonNull MatchCondition[] children) { + this.children = children; + } + + @Override + public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { + for (MatchCondition delegate : this.children) { + if (!delegate.test(id, tags, originalGeometry, projectedGeometry)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionId.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionId.java new file mode 100644 index 00000000..82ef4b42 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionId.java @@ -0,0 +1,34 @@ +package net.buildtheearth.terraplusplus.dataset.osm.match; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.collect.ImmutableSet; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = {@JsonGetter }) +@JsonDeserialize +public final class MatchConditionId implements MatchCondition { + protected final Set ids; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public MatchConditionId( + @JsonProperty(value = "ids", required = true) @NonNull String[] ids) { + this.ids = ImmutableSet.copyOf(Stream.of(ids).map(String::intern).toArray(String[]::new)); + } + + @Override + public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { + return this.ids.contains(id); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionIntersects.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionIntersects.java new file mode 100644 index 00000000..e54f2809 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionIntersects.java @@ -0,0 +1,44 @@ +package net.buildtheearth.terraplusplus.dataset.osm.match; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; + +import java.util.Map; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class MatchConditionIntersects implements MatchCondition { + @Getter(onMethod_ = { @JsonGetter }) + protected final GeographicProjection projection; + @Getter(onMethod_ = { @JsonGetter }) + protected final Geometry geometry; + + protected transient final Bounds2d bb; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + @SneakyThrows(OutOfProjectionBoundsException.class) + public MatchConditionIntersects( + @JsonProperty(value = "projection", required = true) @NonNull GeographicProjection projection, + @JsonProperty(value = "geometry", required = true) @NonNull Geometry geometry) { + this.projection = projection; + this.geometry = geometry; + this.bb = geometry.project(projection::toGeo).bounds(); + } + + @Override + public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { + Bounds2d bounds = originalGeometry.bounds(); + return bounds != null && this.bb.intersects(bounds); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionNot.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionNot.java new file mode 100644 index 00000000..365e5a55 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionNot.java @@ -0,0 +1,33 @@ +package net.buildtheearth.terraplusplus.dataset.osm.match; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; + +import java.util.Map; + +/** + * Inverts the result of a single match condition. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class MatchConditionNot implements MatchCondition { + protected final MatchCondition child; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public MatchConditionNot( + @JsonProperty(value = "child", required = true) @NonNull MatchCondition child) { + this.child = child; + } + + @Override + public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { + return !this.child.test(id, tags, originalGeometry, projectedGeometry); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionOr.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionOr.java new file mode 100644 index 00000000..f825a6a5 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionOr.java @@ -0,0 +1,38 @@ +package net.buildtheearth.terraplusplus.dataset.osm.match; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; + +import java.util.Map; + +/** + * Combines the results of multiple match conditions using a logical OR operation. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class MatchConditionOr implements MatchCondition { + protected final MatchCondition[] children; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public MatchConditionOr( + @JsonProperty(value = "children", required = true) @NonNull MatchCondition[] children) { + this.children = children; + } + + @Override + public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { + for (MatchCondition delegate : this.children) { + if (delegate.test(id, tags, originalGeometry, projectedGeometry)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionTag.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionTag.java new file mode 100644 index 00000000..bc05fc36 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchConditionTag.java @@ -0,0 +1,63 @@ +package net.buildtheearth.terraplusplus.dataset.osm.match; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.StreamSupport; + +/** + * @author DaPorkchop_ + */ +@RequiredArgsConstructor +@JsonDeserialize(builder = MatchConditionTag.Builder.class) +public final class MatchConditionTag implements MatchCondition { + @Getter(onMethod_ = { @JsonValue }) + @NonNull + protected final Map> tags; + + @Override + public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { + for (Map.Entry> entry : this.tags.entrySet()) { + String value = tags.get(entry.getKey()); + if (value == null //the tag isn't set + || (entry.getValue() != null && !entry.getValue().contains(value))) { //the tag's value isn't whitelisted + return false; + } + } + return true; + } + + @JsonPOJOBuilder + public static class Builder { + protected final Map> tags = new Object2ObjectOpenHashMap<>(); + + @JsonAnySetter + private void setter(@NonNull String key, JsonNode node) { + if (node.isNull()) { + this.tags.put(key, null); + } else if (node.isArray()) { + this.tags.put(key, ImmutableSet.copyOf(StreamSupport.stream(node.spliterator(), false).map(JsonNode::asText).map(String::intern).toArray(String[]::new))); + } else { + this.tags.put(key, ImmutableSet.of(node.asText())); + } + } + + public MatchConditionTag build() { + return new MatchConditionTag(this.tags); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchParser.java deleted file mode 100644 index 8021737a..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/MatchParser.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.match; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -public class MatchParser extends JsonParser.Typed { - public static final Map> TYPES = new Object2ObjectOpenHashMap<>(); - - static { - TYPES.put("and", And.class); - TYPES.put("id", Id.class); - TYPES.put("not", Not.class); - TYPES.put("or", Or.class); - - TYPES.put("intersects", Intersects.class); - TYPES.put("tag", Tag.class); - } - - public MatchParser() { - super("match", TYPES); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Not.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Not.java deleted file mode 100644 index acf0fcf8..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Not.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.match; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.io.IOException; -import java.util.Map; - -/** - * Inverts the result of a single match condition. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Not.Parser.class) -@RequiredArgsConstructor -final class Not implements MatchCondition { - @NonNull - protected final MatchCondition delegate; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - return !this.delegate.test(id, tags, originalGeometry, projectedGeometry); - } - - static class Parser extends JsonParser { - @Override - public Not read(JsonReader in) throws IOException { - in.beginObject(); - MatchCondition delegate = TerraConstants.GSON.fromJson(in, MatchCondition.class); - in.endObject(); - return new Not(delegate); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Or.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Or.java deleted file mode 100644 index 44758c9e..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Or.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.match; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; - -import java.io.IOException; -import java.util.Map; - -/** - * Combines the results of multiple match conditions using a logical OR operation. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Or.Parser.class) -@RequiredArgsConstructor -final class Or implements MatchCondition { - @NonNull - protected final MatchCondition[] delegates; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - for (MatchCondition delegate : this.delegates) { - if (delegate.test(id, tags, originalGeometry, projectedGeometry)) { - return true; - } - } - return false; - } - - static class Parser extends MatchParser { - @Override - public MatchCondition read(JsonReader in) throws IOException { - return new Or(readTypedList(in, this).toArray(new MatchCondition[0])); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Tag.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Tag.java deleted file mode 100644 index 8a6d0e9c..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/match/Tag.java +++ /dev/null @@ -1,103 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.osm.match; - -import com.google.common.collect.ImmutableSet; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.io.IOException; -import java.util.Map; -import java.util.Set; - -/** - * Matches a single OpenStreetMap tag. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Tag.Parser.class) -@FunctionalInterface -interface Tag extends MatchCondition { - /** - * Matches tags with the given key, regardless of value. - * - * @author DaPorkchop_ - */ - @RequiredArgsConstructor - final class All implements Tag { - @NonNull - protected final String key; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - return tags.containsKey(this.key); - } - } - - /** - * Matches tags with the given key mapped to any one of the values in a {@link Set}. - * - * @author DaPorkchop_ - */ - @RequiredArgsConstructor - final class Any implements Tag { - @NonNull - protected final String key; - @NonNull - protected final Set expectedValues; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - return this.expectedValues.contains(tags.get(this.key)); - } - } - - /** - * Matches tags with the given key mapped to the given value. - * - * @author DaPorkchop_ - */ - @RequiredArgsConstructor - final class Exactly implements Tag { - @NonNull - protected final String key; - @NonNull - protected final String value; - - @Override - public boolean test(String id, @NonNull Map tags, @NonNull Geometry originalGeometry, @NonNull Geometry projectedGeometry) { - return this.value.equals(tags.get(this.key)); - } - } - - class Parser extends JsonParser { - @Override - public MatchCondition read(JsonReader in) throws IOException { - MatchCondition result; - in.beginObject(); - String key = in.nextName().intern(); - - if (in.peek() == JsonToken.NULL) { //only check if key is set, ignore value - in.nextNull(); - result = new All(key); - } else if (in.peek() == JsonToken.BEGIN_ARRAY) { //check if key is set to any one of the given values - Set expectedValues = ImmutableSet.copyOf(readList(in, reader -> reader.nextString().intern())); - if (expectedValues.isEmpty()) { //if no values are given, no tag can possibly match - result = FALSE; - } else if (expectedValues.size() == 1) { //if only a single value is given there's no need to compare against a set - result = new Exactly(key, expectedValues.iterator().next()); - } else { - result = new Any(key, expectedValues); - } - } else { //check if key is set to exactly the given value - result = new Exactly(key, in.nextString().intern()); - } - - in.endObject(); - return result; - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/package-info.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/package-info.java deleted file mode 100644 index 677a51a5..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/osm/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This package and its subpackages contain all the Gson hackery required to deserialize the OpenStreetMap config properly. - *

- * I apologize profusely to anyone who has to debug this. - * - * @author DaPorkchop_ - */ -package net.buildtheearth.terraplusplus.dataset.osm; \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/BoundedPriorityScalarDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/BoundedPriorityScalarDataset.java new file mode 100644 index 00000000..3626c35d --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/BoundedPriorityScalarDataset.java @@ -0,0 +1,83 @@ +package net.buildtheearth.terraplusplus.dataset.scalar; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.buildtheearth.terraplusplus.dataset.IScalarDataset; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TerraUtils; +import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; + +import java.util.concurrent.CompletableFuture; +import java.util.stream.DoubleStream; + +/** + * @author DaPorkchop_ + */ +@RequiredArgsConstructor +public class BoundedPriorityScalarDataset implements Bounds2d, IScalarDataset, Comparable { + @NonNull + protected final IScalarDataset delegate; + @NonNull + protected final Bounds2d bounds; + @NonNull + protected final double[] priorities; + + // IScalarDataset + + @Override + public CompletableFuture getAsync(double lon, double lat) throws OutOfProjectionBoundsException { + return this.delegate.getAsync(lon, lat); + } + + @Override + public CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds, int sizeX, int sizeZ) throws OutOfProjectionBoundsException { + return this.delegate.getAsync(bounds, sizeX, sizeZ); + } + + @Override + public CompletableFuture getAsync(@NonNull PointArray2D points) throws OutOfProjectionBoundsException { + return this.delegate.getAsync(points); + } + + @Override + public double[] degreesPerSample() { + return this.delegate.degreesPerSample(); + } + + // Comparable + + @Override + public int compareTo(BoundedPriorityScalarDataset o) { + int d = -TerraUtils.compareDoubleArrays(this.priorities, o.priorities); + if (d == 0) { //priorities are equal, compare by resolution + double dps0 = DoubleStream.of(this.degreesPerSample()).min().getAsDouble(); + double dps1 = DoubleStream.of(o.degreesPerSample()).min().getAsDouble(); + d = Double.compare(dps0, dps1); + } + return d; + } + + // Bounds2d + + @Override + public double minX() { + return this.bounds.minX(); + } + + @Override + public double maxX() { + return this.bounds.maxX(); + } + + @Override + public double minZ() { + return this.bounds.minZ(); + } + + @Override + public double maxZ() { + return this.bounds.maxZ(); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ConfigurableDoubleTiledDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ConfigurableDoubleTiledDataset.java deleted file mode 100644 index 29c94f63..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ConfigurableDoubleTiledDataset.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.scalar; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.netty.buffer.ByteBuf; -import lombok.Getter; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.config.scalarparse.d.DoubleScalarParser; -import net.buildtheearth.terraplusplus.dataset.BlendMode; -import net.buildtheearth.terraplusplus.projection.GeographicProjection; - -/** - * Implementation of {@link DoubleTiledDataset} whose behavior is defined by JSON configuration. - * - * @author DaPorkchop_ - */ -@JsonDeserialize -@JsonSerialize -@Getter(onMethod_ = { @JsonGetter }) -public class ConfigurableDoubleTiledDataset extends DoubleTiledDataset { - protected final String[] urls; - protected final DoubleScalarParser parse; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public ConfigurableDoubleTiledDataset( - @JsonProperty(value = "urls", required = true) @NonNull String[] urls, - @JsonProperty(value = "resolution", required = true) int resolution, - @JsonProperty(value = "blend", required = true) @NonNull BlendMode blend, - @JsonProperty(value = "parse", required = true) @NonNull DoubleScalarParser parse, - @JsonProperty(value = "projection", required = true) @NonNull GeographicProjection projection) { - super(projection, resolution, blend); - - this.urls = urls; - this.parse = parse; - } - - @Override - protected String[] urls(int tileX, int tileZ) { - return this.urls; - } - - @Override - protected double[] decode(int tileX, int tileZ, @NonNull ByteBuf data) throws Exception { - return this.parse.parse(this.resolution, data); - } - - @Override - @JsonGetter - public int resolution() { - return super.resolution(); - } - - @Override - @JsonGetter - public BlendMode blend() { - return super.blend(); - } - - @Override - @JsonGetter - public GeographicProjection projection() { - return super.projection(); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/DoubleTiledDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/DoubleTiledDataset.java index 23444590..17b0cc18 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/DoubleTiledDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/DoubleTiledDataset.java @@ -13,14 +13,21 @@ import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; import net.buildtheearth.terraplusplus.util.IntToDoubleBiFunction; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; import net.daporkchop.lib.common.math.BinMath; +import net.daporkchop.lib.common.pool.array.ArrayAllocator; +import net.daporkchop.lib.common.util.PorkUtil; import net.minecraft.util.math.ChunkPos; import java.util.Arrays; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import static java.lang.Math.*; +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; import static net.daporkchop.lib.common.util.PValidation.*; /** @@ -37,11 +44,21 @@ public abstract class DoubleTiledDataset extends TiledHttpDataset impl protected final BlendMode blend; protected final int resolution; + protected final double[] degreesPerSample; + public DoubleTiledDataset(@NonNull GeographicProjection projection, int resolution, @NonNull BlendMode blend) { super(projection, 1.0d / resolution); + checkArg(resolution == 256, "unsupported resolution: %d (expected: 256)", resolution); this.resolution = resolution; this.blend = blend; + + double[] bounds = this.projection().bounds(); + double[] boundsGeo = this.projection().boundsGeo(); + this.degreesPerSample = new double[]{ + abs(boundsGeo[2] - boundsGeo[0]) / abs(bounds[2] - bounds[0]), + abs(boundsGeo[3] - boundsGeo[1]) / abs(bounds[3] - bounds[1]) + }; } @Override @@ -73,7 +90,7 @@ public Double apply(Void unused) { //stage 2: actually compute the value now tha @Override public CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds, int sizeX, int sizeZ) throws OutOfProjectionBoundsException { if (notNegative(sizeX, "sizeX") == 0 | notNegative(sizeZ, "sizeZ") == 0) { //no input points -> no output points, ez - return CompletableFuture.completedFuture(new double[0]); + return CompletableFuture.completedFuture(EMPTY_DOUBLE_ARRAY); } class State extends AbstractState { @@ -117,6 +134,48 @@ public double[] apply(Void unused) { //stage 2: actually compute the values now return new State(localBounds, paddedLocalBounds).future(); } + @Override + public CompletableFuture getAsync(@NonNull PointArray2D points) throws OutOfProjectionBoundsException { + if (points.size() == 0) { + return CompletableFuture.completedFuture(EMPTY_DOUBLE_ARRAY); + } + + class State extends AbstractState { + protected final PointArray2D points; + + public State(Bounds2d bounds, PointArray2D points) { + super(bounds); + this.points = points; + } + + @Override + public double[] apply(Void unused) { //stage 2: actually compute the values now that the tiles have been fetched + BlendMode blend = DoubleTiledDataset.this.blend; + + int size = this.points.size(); + + //read coordinate values into an array + ArrayAllocator alloc = DOUBLE_ALLOC.get(); + double[] pointsBuffer = alloc.atLeast(this.points.totalValueSize()); + this.points.points(pointsBuffer, 0); + + //sample the value at each point and write them into a new array + double[] out = new double[size]; + for (int i = 0; i < size; i++) { + out[i] = blend.get(pointsBuffer[i * 2], pointsBuffer[i * 2 + 1], this); + } + + alloc.release(pointsBuffer); + return out; + } + } + + PointArray2D projectedPoints = (PointArray2D) points.convert(SISHelper.projectedCRS(this.projection), 0.1d); + Bounds2d paddedLocalBounds = SISHelper.toBounds(projectedPoints.envelope()).expand(this.blend.size).validate(this.projection, false); + + return new State(paddedLocalBounds, projectedPoints).future(); + } + @RequiredArgsConstructor protected abstract class AbstractState implements Function, IntToDoubleBiFunction { final Long2ObjectMap loadedTiles = new Long2ObjectOpenHashMap<>(); @@ -134,14 +193,14 @@ public double apply(int x, int z) { //gets raw sample values to be used in blend } public CompletableFuture future() { - ChunkPos[] tilePositions = this.paddedLocalBounds.toTiles(TILE_SIZE); + TilePos[] tilePositions = this.paddedLocalBounds.toTiles(TILE_SIZE, 0); //TODO: actually use the correct level return CompletableFuture.allOf(Arrays.stream(tilePositions) .map(pos -> DoubleTiledDataset.this.getAsync(pos) .thenApply(tile -> { //put tile directly into map when it's loaded //synchronize because we can't be certain that all of the futures will be completed by the same thread synchronized (this.loadedTiles) { - this.loadedTiles.put(BinMath.packXY(pos.x, pos.z), tile); + this.loadedTiles.put(BinMath.packXY(pos.x(), pos.z()), tile); } return tile; })) diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/MultiScalarDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/MultiScalarDataset.java index 0ff9c80b..b22585df 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/MultiScalarDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/MultiScalarDataset.java @@ -1,76 +1,42 @@ package net.buildtheearth.terraplusplus.dataset.scalar; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import lombok.Getter; import lombok.NonNull; -import lombok.SneakyThrows; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.config.condition.DoubleCondition; import net.buildtheearth.terraplusplus.dataset.IScalarDataset; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; -import net.buildtheearth.terraplusplus.util.IntRange; import net.buildtheearth.terraplusplus.util.bvh.BVH; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; -import net.buildtheearth.terraplusplus.util.http.Disk; -import net.daporkchop.lib.common.function.io.IOFunction; -import net.daporkchop.lib.common.function.throwing.EFunction; - -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; + import java.util.Arrays; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; -import java.util.stream.Stream; +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; import static net.daporkchop.lib.common.util.PValidation.*; /** - * Implementation of {@link IScalarDataset} which can sample from multiple {@link IScalarDataset}s and combine the results. + * Implementation of {@link IScalarDataset} which can sample from multiple {@link BoundedPriorityScalarDataset}s and combine the results. + *

+ * Datasets are only sampled if their bounding polygon intersects the query bounds, and are sampled in order of their priority. * * @author DaPorkchop_ */ public class MultiScalarDataset implements IScalarDataset { - protected final BVH bvh; - - @SneakyThrows(IOException.class) - public MultiScalarDataset(@NonNull String name, boolean useDefault) { - List configSources = new ArrayList<>(); - if (useDefault) { //add default configuration - configSources.add(MultiScalarDataset.class.getResource(name + ".json5")); - } + protected final BVH bvh; - try (Stream stream = Files.list(Files.createDirectories(Disk.configFile(name)))) { - stream.filter(Files::isRegularFile) - .filter(p -> p.getFileName().toString().matches(".*\\.json5?$")) - .map(Path::toUri).map((EFunction) URI::toURL) - .forEach(configSources::add); - } - - this.bvh = BVH.of(configSources.stream() - .map((IOFunction) url -> TerraConstants.JSON_MAPPER.readValue(url, WrappedDataset[].class)) - .flatMap(Arrays::stream) - .toArray(WrappedDataset[]::new)); + public MultiScalarDataset(@NonNull BoundedPriorityScalarDataset... sources) { + this.bvh = BVH.of(sources); } @Override public CompletableFuture getAsync(double lon, double lat) throws OutOfProjectionBoundsException { - WrappedDataset[] datasets = this.bvh.getAllIntersecting(Bounds2d.of(lon, lon, lat, lat)).toArray(new WrappedDataset[0]); + BoundedPriorityScalarDataset[] datasets = this.bvh.getAllIntersecting(Bounds2d.of(lon, lon, lat, lat)).toArray(new BoundedPriorityScalarDataset[0]); if (datasets.length == 0) { //no matching datasets! return CompletableFuture.completedFuture(Double.NaN); } else if (datasets.length == 1) { //only one dataset matches - if (datasets[0].condition == null) { //if it doesn't have a condition, there's no reason to do any merging - return datasets[0].dataset.getAsync(lon, lat); - } + return datasets[0].getAsync(lon, lat); } Arrays.sort(datasets); //ensure datasets are in priority order @@ -82,7 +48,7 @@ class State implements BiConsumer { public void accept(Double v, Throwable cause) { if (cause != null) { this.future.completeExceptionally(cause); - } else if (!Double.isNaN(v) && datasets[this.i].test(v)) { //if the value in the input array is accepted, use it as the output + } else if (!Double.isNaN(v)) { //if the value in the input array is accepted, use it as the output this.future.complete(v); } else { //sample the next dataset this.advance(); @@ -92,7 +58,7 @@ public void accept(Double v, Throwable cause) { private void advance() { if (++this.i < datasets.length) { try { - datasets[this.i].dataset.getAsync(lon, lat).whenComplete(this); + datasets[this.i].getAsync(lon, lat).whenComplete(this); } catch (OutOfProjectionBoundsException e) { this.future.completeExceptionally(e); } @@ -110,16 +76,14 @@ private void advance() { @Override public CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds, int sizeX, int sizeZ) throws OutOfProjectionBoundsException { if (notNegative(sizeX, "sizeX") == 0 | notNegative(sizeZ, "sizeZ") == 0) { //no input points -> no output points, ez - return CompletableFuture.completedFuture(new double[0]); + return CompletableFuture.completedFuture(EMPTY_DOUBLE_ARRAY); } - WrappedDataset[] datasets = this.bvh.getAllIntersecting(bounds).toArray(new WrappedDataset[0]); + BoundedPriorityScalarDataset[] datasets = this.bvh.getAllIntersecting(bounds).toArray(new BoundedPriorityScalarDataset[0]); if (datasets.length == 0) { //no matching datasets! return CompletableFuture.completedFuture(null); } else if (datasets.length == 1) { //only one dataset matches - if (datasets[0].condition == null) { //if it doesn't have a condition, there's no reason to do any merging - return datasets[0].dataset.getAsync(bounds, sizeX, sizeZ); - } + return datasets[0].getAsync(bounds, sizeX, sizeZ); } Arrays.sort(datasets); //ensure datasets are in priority order @@ -139,12 +103,10 @@ public void accept(double[] data, Throwable cause) { Arrays.fill(this.out = out = new double[sizeX * sizeZ], Double.NaN); } - WrappedDataset dataset = datasets[this.i]; - for (int i = 0; i < sizeX * sizeZ; i++) { if (Double.isNaN(out[i])) { //if value in output array is NaN, consider replacing it double v = data[i]; - if (!Double.isNaN(v) && dataset.test(v)) { //if the value in the input array is accepted, use it as the output + if (!Double.isNaN(v)) { //if the value in the input array is accepted, use it as the output out[i] = v; if (--this.remaining == 0) { //if no samples are left to process, we're done! this.future.complete(out); @@ -160,7 +122,7 @@ public void accept(double[] data, Throwable cause) { private void advance() { if (++this.i < datasets.length) { try { - datasets[this.i].dataset.getAsync(bounds, sizeX, sizeZ).whenComplete(this); + datasets[this.i].getAsync(bounds, sizeX, sizeZ).whenComplete(this); } catch (OutOfProjectionBoundsException e) { this.future.completeExceptionally(e); } @@ -175,61 +137,68 @@ private void advance() { return state.future; } - /** - * Wrapper around a dataset with a bounding box. - * - * @author DaPorkchop_ - */ - @JsonDeserialize - @JsonSerialize - @Getter - public static class WrappedDataset implements Bounds2d, Comparable, DoubleCondition { - @Getter(onMethod_ = { @JsonGetter }) - protected final IScalarDataset dataset; - @Getter(onMethod_ = { @JsonGetter }) - protected final DoubleCondition condition; - @Getter(onMethod_ = { @JsonGetter }) - protected final IntRange zooms; //TODO: use this - - protected final double minX; - protected final double maxX; - protected final double minZ; - protected final double maxZ; - - @Getter(onMethod_ = { @JsonGetter }) - protected final double priority; - - @JsonCreator - public WrappedDataset( - @JsonProperty(value = "dataset", required = true) @NonNull IScalarDataset dataset, - @JsonProperty(value = "bounds", required = true) @NonNull Bounds2d bounds, - @JsonProperty(value = "zooms", required = true) @NonNull IntRange zooms, - @JsonProperty(value = "priority", defaultValue = "0.0") double priority, - @JsonProperty("condition") DoubleCondition condition) { - this.dataset = dataset; - this.condition = condition; - this.zooms = zooms; - this.priority = priority; - - this.minX = bounds.minX(); - this.maxX = bounds.maxX(); - this.minZ = bounds.minZ(); - this.maxZ = bounds.maxZ(); + @Override + public CompletableFuture getAsync(@NonNull PointArray2D points) throws OutOfProjectionBoundsException { + int size = points.size(); + if (size == 0) { //no input points -> no output points, ez + return CompletableFuture.completedFuture(EMPTY_DOUBLE_ARRAY); } - @Override - public int compareTo(WrappedDataset o) { - return -Double.compare(this.priority, o.priority); + BoundedPriorityScalarDataset[] datasets = this.bvh.getAllIntersecting(SISHelper.toBounds(points.envelope())).toArray(new BoundedPriorityScalarDataset[0]); + if (datasets.length == 0) { //no matching datasets! + return CompletableFuture.completedFuture(null); + } else if (datasets.length == 1) { //only one dataset matches + return datasets[0].getAsync(points); } + Arrays.sort(datasets); //ensure datasets are in priority order - @Override - public boolean test(double value) { - return this.condition == null || this.condition.test(value); - } + class State implements BiConsumer { + final CompletableFuture future = new CompletableFuture<>(); + double[] out; + int remaining = size; + int i = -1; + + @Override + public void accept(double[] data, Throwable cause) { + if (cause != null) { + this.future.completeExceptionally(cause); + } else if (data != null) { //if the array is null, it's as if it were an array of NaNs - nothing would be set, we simply skip it + double[] out = this.out; + if (out == null) { //ensure the destination array is set + Arrays.fill(this.out = out = new double[size], Double.NaN); + } - @JsonGetter("bounds") - public Bounds2d bounds() { - return Bounds2d.of(this.minX, this.maxX, this.minZ, this.maxZ); + for (int i = 0; i < size; i++) { + if (Double.isNaN(out[i])) { //if value in output array is NaN, consider replacing it + double v = data[i]; + if (!Double.isNaN(v)) { //if the value in the input array is accepted, use it as the output + out[i] = v; + if (--this.remaining == 0) { //if no samples are left to process, we're done! + this.future.complete(out); + return; + } + } + } + } + } + this.advance(); + } + + private void advance() { + if (++this.i < datasets.length) { + try { + datasets[this.i].getAsync(points).whenComplete(this); + } catch (OutOfProjectionBoundsException e) { + this.future.completeExceptionally(e); + } + } else { //no datasets remain, complete the future successfully with whatever value we currently have + this.future.complete(this.out); + } + } } + + State state = new State(); + state.advance(); + return state.future; } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/MultiresScalarDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/MultiresScalarDataset.java new file mode 100644 index 00000000..470f0c3e --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/MultiresScalarDataset.java @@ -0,0 +1,76 @@ +package net.buildtheearth.terraplusplus.dataset.scalar; + +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.IScalarDataset; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.stream.DoubleStream; + +import static java.lang.Math.*; +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * Implementation of {@link IScalarDataset} which combines multiple datasets that all cover the same area at varying resolutions. + *

+ * Point queries will be resolved using the highest-resolution dataset. + *

+ * Area queries will be resolved using the dataset whose resolution is closest to that of the requested area (rounded up). + * + * @author DaPorkchop_ + */ +public class MultiresScalarDataset implements IScalarDataset { + protected final NavigableMap datasetsByDegreesPerSample; + protected final IScalarDataset maxResDataset; + + public MultiresScalarDataset(@NonNull IScalarDataset... datasets) { + checkArg(datasets.length >= 1, "at least one dataset must be given!"); + + this.datasetsByDegreesPerSample = new TreeMap<>(); + for (IScalarDataset dataset : datasets) { + double[] degreesPerSample = dataset.degreesPerSample(); + this.datasetsByDegreesPerSample.put(min(degreesPerSample[0], degreesPerSample[1]), dataset); + } + this.maxResDataset = this.datasetsByDegreesPerSample.firstEntry().getValue(); + } + + @Override + public CompletableFuture getAsync(double lon, double lat) throws OutOfProjectionBoundsException { + return this.maxResDataset.getAsync(lon, lat); + } + + @Override + public CompletableFuture getAsync(@NonNull CornerBoundingBox2d bounds, int sizeX, int sizeZ) throws OutOfProjectionBoundsException { + //calculate the degrees/sample of the query bounds + double dps = bounds.avgDegreesPerSample(sizeX, sizeZ); + + //find the dataset whose resolution is closest (rounding up) + Map.Entry entry = this.datasetsByDegreesPerSample.floorEntry(dps); + //fall back to max resolution dataset if none match (the query BB is higher-res than the best dataset) + IScalarDataset dataset = entry != null ? entry.getValue() : this.maxResDataset; + return dataset.getAsync(bounds, sizeX, sizeZ); + } + + @Override + public CompletableFuture getAsync(@NonNull PointArray2D points) throws OutOfProjectionBoundsException { + //calculate the degrees/sample of the query points + double dps = DoubleStream.of(points.convert(TPP_GEO_CRS, 0.1d).estimatedPointDensity()).average().getAsDouble(); + + //find the dataset whose resolution is closest (rounding up) + Map.Entry entry = this.datasetsByDegreesPerSample.floorEntry(dps); + //fall back to max resolution dataset if none match (the query BB is higher-res than the best dataset) + IScalarDataset dataset = entry != null ? entry.getValue() : this.maxResDataset; + return dataset.getAsync(points); + } + + @Override + public double[] degreesPerSample() { + return this.maxResDataset.degreesPerSample(); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ScalarDatasetConfigurationParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ScalarDatasetConfigurationParser.java new file mode 100644 index 00000000..403a6466 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ScalarDatasetConfigurationParser.java @@ -0,0 +1,208 @@ +package net.buildtheearth.terraplusplus.dataset.scalar; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import net.buildtheearth.terraplusplus.dataset.IScalarDataset; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.format.TileFormat; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.mode.TileMode; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.TerraUtils; +import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.http.Http; +import net.buildtheearth.terraplusplus.util.jackson.IntListDeserializer; + +import java.io.DataInput; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; +import static net.daporkchop.lib.common.util.PValidation.*; +import static net.daporkchop.lib.common.util.PorkUtil.*; + +/** + * Helper class for parsing scalar datasets using the new format introduced in Terra++ 2.0. + * + * @author DaPorkchop_ + */ +@UtilityClass +public class ScalarDatasetConfigurationParser { + public CompletableFuture loadAndMergeDatasetsFromManifests(@NonNull Stream srcs) { + return TerraUtils.mergeFuturesAsync(srcs.map(ScalarDatasetConfigurationParser::loadDatasetsFromManifest)) + .thenApply(list -> merge(list.stream().flatMap(Stream::of))); + } + + public CompletableFuture loadDatasetsFromManifest(@NonNull String[] src) { + return Http.getFirst(Http.suffixAll(src, "manifest.json"), Manifest::parse) + .thenComposeAsync(manifest -> { + if (manifest == null) { + throw new IllegalStateException("unable to find manifest file at any of the given source URLs: " + Arrays.toString(Http.suffixAll(src, "manifest.json"))); + } + + for (int i = 0; i < manifest.datasets.length; i++) { //flatten all URLs + manifest.datasets[i] = Http.flatten(src, manifest.datasets[i]); + } + + return TerraUtils.mergeFuturesAsync(Stream.of(manifest.datasets).map(datasetUrls -> loadDataset(manifest.priority, datasetUrls))) + .thenApply(list -> list.toArray(new BoundedPriorityScalarDataset[0])); + }); + } + + public CompletableFuture loadDataset(double priority, @NonNull String... urls) { + return Http.getFirst(Http.suffixAll(urls, "dataset.json"), Dataset::parse) + .thenApplyAsync(dataset -> { + if (dataset == null) { + throw new IllegalStateException("unable to find dataset file at any of the given source URLs: " + Arrays.toString(Http.suffixAll(urls, "dataset.json"))); + } + + return dataset.toScalar(urls, priority); + }); + } + + public IScalarDataset merge(@NonNull Stream datasets) { + return new MultiScalarDataset(datasets.toArray(BoundedPriorityScalarDataset[]::new)); + } + + /** + * Representation of a multi-dataset manifest JSON object. + * + * @author DaPorkchop_ + */ + @JsonDeserialize + protected static final class Manifest { + @SneakyThrows(IOException.class) + public static Manifest parse(@NonNull ByteBuf data) { + return JSON_MAPPER.readValue((DataInput) new ByteBufInputStream(data), Manifest.class); + } + + public final String[][] datasets; + public final double priority; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Manifest( + @JsonProperty(value = "version", required = true) @NonNull Version version, //validation is done in Version constructor during deserialization + @JsonProperty(value = "datasets", required = true) @NonNull String[][] datasets, + @JsonProperty(value = "priority", required = true) double priority) { + for (int i = 0; i < datasets.length; i++) { + checkArg(datasets[i].length >= 1, "dataset must have at least one URL! @datasets[%d]", i); + } + this.datasets = datasets; + this.priority = priority; + } + } + + /** + * Representation of an individual dataset JSON object. + * + * @author DaPorkchop_ + */ + @JsonDeserialize + protected static final class Dataset { + @SneakyThrows(IOException.class) + public static Dataset parse(@NonNull ByteBuf data) { + return JSON_MAPPER.readValue((DataInput) new ByteBufInputStream(data), Dataset.class); + } + + protected final int[] zoom; + protected final Tiles tiles; + protected final Bounds bounds; + + protected final String[] forceUrls; + protected final Double priority; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Dataset( + @JsonProperty(value = "version", required = true) @NonNull Version version, //validation is done in Version constructor during deserialization + @JsonProperty(value = "zoom", required = true) @JsonDeserialize(using = IntListDeserializer.class) @NonNull int[] zoom, + @JsonProperty(value = "tiles", required = true) @NonNull Tiles tiles, + @JsonProperty(value = "bounds", required = true) @NonNull Bounds bounds, + @JsonProperty(value = "force_urls", required = false) String[] forceUrls, + @JsonProperty(value = "priority", required = false) Double priority) { + checkArg(zoom.length >= 1, "at least one zoom level must be set!"); + + this.zoom = zoom; + this.tiles = tiles; + this.bounds = bounds; + + this.forceUrls = forceUrls; + this.priority = priority; + } + + public BoundedPriorityScalarDataset toScalar(@NonNull String[] urlsIn, double priority) { + String[] urls = fallbackIfNull(this.forceUrls, urlsIn); + + return new BoundedPriorityScalarDataset( + new MultiresScalarDataset(IntStream.of(this.zoom).mapToObj(zoom -> this.tiles.toScalar(urls, zoom)).toArray(IScalarDataset[]::new)), + this.bounds.build(), + new double[]{ priority, fallbackIfNull(this.priority, 0.0d) }); + } + + @JsonDeserialize + protected static class Tiles { + protected final GeographicProjection projection; + protected final int resolution; + protected final TileMode mode; + protected final TileFormat format; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Tiles( + @JsonProperty(value = "projection", required = true) @NonNull GeographicProjection projection, + @JsonProperty(value = "resolution", required = true) int resolution, + @JsonProperty(value = "mode", required = true) @NonNull TileMode mode, + @JsonProperty(value = "format", required = true) @NonNull TileFormat format) { + this.projection = projection; + this.resolution = resolution; + this.mode = mode; + this.format = format; + } + + public IScalarDataset toScalar(@NonNull String[] urls, int zoom) { + return new ZoomedTiledScalarDataset(urls, this.resolution, zoom, this.mode, this.format, this.projection); + } + } + + @JsonDeserialize + protected static class Bounds { + protected final GeographicProjection projection; + protected final Geometry geometry; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Bounds( + @JsonProperty(value = "projection", required = true) @NonNull GeographicProjection projection, + @JsonProperty(value = "geometry", required = true) @NonNull Geometry geometry) { + this.projection = projection; + this.geometry = geometry; + } + + @SneakyThrows(OutOfProjectionBoundsException.class) + public Bounds2d build() { + return this.geometry.project(this.projection::toGeo).bounds(); + } + } + } + + /** + * Dummy class which validates that the dataset version is set to 1.0. + * + * @author DaPorkchop_ + */ + @JsonDeserialize + protected static final class Version { + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public Version( + @JsonProperty(value = "major", required = true) int major, + @JsonProperty(value = "minor", required = true) int minor) { + checkArg(major == 1, "unsupported dataset version: %d.%d", major, minor); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ZoomedTiledScalarDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ZoomedTiledScalarDataset.java new file mode 100644 index 00000000..0a104a45 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/ZoomedTiledScalarDataset.java @@ -0,0 +1,51 @@ +package net.buildtheearth.terraplusplus.dataset.scalar; + +import io.netty.buffer.ByteBuf; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.BlendMode; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.format.TileFormat; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.mode.TileMode; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.transform.ScaleProjectionTransform; +import net.buildtheearth.terraplusplus.util.http.Http; + +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * Implementation of {@link DoubleTiledDataset} which combines a {@link TileMode}, {@link TileFormat}, tile resolution, and some number of base URLs at a fixed zoom level. + * + * @author DaPorkchop_ + */ +public class ZoomedTiledScalarDataset extends DoubleTiledDataset { + protected static GeographicProjection scaleProjection(@NonNull GeographicProjection projection, int zoom) { + if (notNegative(zoom, "zoom") == 0) { + return projection; + } else { + return new ScaleProjectionTransform(projection, 1 << zoom, 1 << zoom); + } + } + + protected final String[] urls; + protected final TileMode mode; + protected final TileFormat format; + protected final int zoom; + + public ZoomedTiledScalarDataset(@NonNull String[] urls, int resolution, int zoom, @NonNull TileMode mode, @NonNull TileFormat format, @NonNull GeographicProjection projection) { + super(scaleProjection(projection, zoom), resolution, BlendMode.CUBIC); + + this.urls = urls; + this.mode = mode; + this.format = format; + this.zoom = zoom; + } + + @Override + protected String[] urls(int tileX, int tileZ, int zoom) { + return Http.suffixAll(this.urls, this.mode.path(tileX, tileZ, this.zoom)); //TODO: use proper zoom parameter + } + + @Override + protected double[] decode(int tileX, int tileZ, @NonNull ByteBuf data) throws Exception { + return this.format.parse(data, this.resolution); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormat.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormat.java new file mode 100644 index 00000000..305ab37c --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormat.java @@ -0,0 +1,32 @@ +package net.buildtheearth.terraplusplus.dataset.scalar.tile.format; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import io.netty.buffer.ByteBuf; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; + +/** + * @author DaPorkchop_ + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "name") +@JsonTypeIdResolver(TileFormat.TypeIdResolver.class) +@JsonDeserialize +@FunctionalInterface +public interface TileFormat { + /** + * Parses the data stored in the given buffer into a {@code double[]}. + * + * @param buf the data + * @param resolution the expected resolution of the data + * @return the parsed data + */ + double[] parse(@NonNull ByteBuf buf, int resolution); + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.TILE_FORMATS); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormatTerrariumPng.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormatTerrariumPng.java new file mode 100644 index 00000000..78edc34d --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormatTerrariumPng.java @@ -0,0 +1,36 @@ +package net.buildtheearth.terraplusplus.dataset.scalar.tile.format; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import lombok.NonNull; +import lombok.SneakyThrows; + +import javax.imageio.ImageIO; +import java.io.IOException; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class TileFormatTerrariumPng implements TileFormat { + @Override + @SneakyThrows(IOException.class) + public double[] parse(@NonNull ByteBuf buf, int resolution) { + int[] arr = ImageIO.read(new ByteBufInputStream(buf)).getRGB(0, 0, resolution, resolution, null, 0, resolution); + + double[] out = new double[resolution * resolution]; + for (int i = 0; i < resolution * resolution; i++) { + int c = arr[i]; + if ((c >>> 24) == 0xFF) { + out[i] = ((c & 0x00FFFFFF) * (1.0d / 256.0d)) - 32768.0d; + } else { //pixel isn't opaque + out[i] = Double.NaN; + } + } + + TileTransform.FLIP_Y.process(out, resolution); + + return out; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormatTiff.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormatTiff.java new file mode 100644 index 00000000..ac2a66f7 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileFormatTiff.java @@ -0,0 +1,162 @@ +package net.buildtheearth.terraplusplus.dataset.scalar.tile.format; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import lombok.Getter; +import lombok.NonNull; +import lombok.SneakyThrows; +import org.apache.commons.imaging.FormatCompliance; +import org.apache.commons.imaging.ImageReadException; +import org.apache.commons.imaging.ImagingException; +import org.apache.commons.imaging.common.ImageBuilder; +import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream; +import org.apache.commons.imaging.formats.tiff.TiffContents; +import org.apache.commons.imaging.formats.tiff.TiffDirectory; +import org.apache.commons.imaging.formats.tiff.TiffField; +import org.apache.commons.imaging.formats.tiff.TiffRasterData; +import org.apache.commons.imaging.formats.tiff.TiffReader; +import org.apache.commons.imaging.formats.tiff.constants.GdalLibraryTagConstants; +import org.apache.commons.imaging.formats.tiff.constants.TiffConstants; +import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; +import sun.awt.image.IntegerComponentRaster; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.Collections; + +import static net.daporkchop.lib.common.util.PValidation.*; +import static net.daporkchop.lib.common.util.PorkUtil.*; + +/** + * {@link TileFormat} implementation for parsing scalar data tiles from TIFF images. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public class TileFormatTiff implements TileFormat { + private static final double[] MODEL_PIXEL_SCALE_DEFAULT = { 1.0d, 1.0d }; + + protected final Type type; + protected final int band; + + protected final double factor; + protected final double offset; + + protected final TileTransform transform; + + @JsonCreator + public TileFormatTiff( + @JsonProperty(value = "type", required = true) @NonNull Type type, + @JsonProperty(value = "band", required = true) int band, + @JsonProperty("factor") Double factor, + @JsonProperty("offset") Double offset, + @JsonProperty(value = "transform", required = true) @NonNull TileTransform transform) { + this.type = type; + this.band = notNegative(band, "band"); + this.factor = fallbackIfNull(factor, 1.0d); + this.offset = fallbackIfNull(offset, 0.0d); + this.transform = transform; + } + + @Override + @SneakyThrows({ ImagingException.class, IOException.class }) + public double[] parse(@NonNull ByteBuf buf, int resolution) { + TiffContents contents = new TiffReader(false) + .readDirectories(new ByteSourceInputStream(new ByteBufInputStream(buf), ""), true, FormatCompliance.getDefault()); + TiffDirectory directory = contents.directories.get(this.band); + + double[] out = new double[resolution * resolution]; + this.type.getData(directory, out, resolution); + + for (int i = 0; i < out.length; i++) { //scale and offset values (this will likely be auto-vectorized) + out[i] = out[i] * this.factor + this.offset; + } + + this.transform.process(out, resolution); + return out; + } + + /** + * The different TIFF raster types. + * + * @author DaPorkchop_ + */ + public enum Type { + Byte { + @Override + protected void getData(@NonNull TiffDirectory directory, @NonNull double[] dst, int resolution) throws ImagingException, IOException { + BufferedImage img = directory.getTiffImage(); + int w = img.getWidth(); + int h = img.getHeight(); + checkArg(w == resolution && h == resolution, "invalid image resolution: %dx%d (expected: %dx%3$d)", w, h, resolution); + + TiffField nodataField = directory.findField(GdalLibraryTagConstants.EXIF_TAG_GDAL_NO_DATA); + int nodata = nodataField != null ? Integer.parseInt(nodataField.getStringValue()) : -1; + + int[] data = ((IntegerComponentRaster) img.getRaster()).getDataStorage(); + checkArg(data.length == dst.length, "data length invalid?!?"); + + for (int i = 0; i < dst.length; i++) { + int v = data[i] & 0xFF; + dst[i] = v != nodata ? v : Double.NaN; + } + } + }, + Int16 { + @Override + protected void getData(@NonNull TiffDirectory directory, @NonNull double[] dst, int resolution) throws ImagingException, IOException { + BufferedImage img = directory.getTiffImage(Collections.singletonMap(TiffConstants.PARAM_KEY_CUSTOM_PHOTOMETRIC_INTERPRETER, + new PhotometricInterpreter(0, null, 0, 0, 0) { + @Override + public void interpretPixel(ImageBuilder imageBuilder, int[] samples, int x, int y) throws ImageReadException, IOException { + imageBuilder.setRGB(x, y, (short) samples[0]); + } + + @Override + public boolean isRaw() { + return true; + } + })); + int w = img.getWidth(); + int h = img.getHeight(); + checkArg(w == resolution && h == resolution, "invalid image resolution: %dx%d (expected: %dx%3$d)", w, h, resolution); + + TiffField nodataField = directory.findField(GdalLibraryTagConstants.EXIF_TAG_GDAL_NO_DATA); + int nodata = nodataField != null ? Integer.parseInt(nodataField.getStringValue()) : -1; + + int[] data = ((IntegerComponentRaster) img.getRaster()).getDataStorage(); + checkArg(data.length == dst.length, "data length invalid?!?"); + + for (int i = 0; i < dst.length; i++) { + int v = data[i]; + dst[i] = v != nodata ? v : Double.NaN; + } + } + }, + Float32 { + @Override + protected void getData(@NonNull TiffDirectory directory, @NonNull double[] dst, int resolution) throws ImagingException, IOException { + TiffRasterData data = directory.getFloatingPointRasterData(null); + int w = data.getWidth(); + int h = data.getHeight(); + checkArg(w == resolution && h == resolution, "invalid image resolution: %dx%d (expected: %dx%3$d)", w, h, resolution); + checkArg(data.getData().length == dst.length, "data length invalid?!?"); + + TiffField nodataField = directory.findField(GdalLibraryTagConstants.EXIF_TAG_GDAL_NO_DATA); + float nodata = nodataField != null ? Float.parseFloat(nodataField.getStringValue()) : Float.NaN; + + for (int i = 0; i < dst.length; i++) { + float v = data.getData()[i]; + dst[i] = v != nodata ? v : Double.NaN; + } + } + }; + + protected abstract void getData(@NonNull TiffDirectory directory, @NonNull double[] dst, int resolution) throws ImagingException, IOException; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileTransform.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileTransform.java new file mode 100644 index 00000000..80b30e0c --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/format/TileTransform.java @@ -0,0 +1,61 @@ +package net.buildtheearth.terraplusplus.dataset.scalar.tile.format; + +import lombok.NonNull; + +/** + * Helper class for transformations on {@code double[]} tiles. + * + * @author DaPorkchop_ + */ +public enum TileTransform { + NONE { + @Override + public void process(@NonNull double[] arr, int resolution) { + //no-op + } + }, + SWAP_AXES { + @Override + public void process(@NonNull double[] arr, int resolution) { + for (int i = 1; i < resolution; i++) { + for (int j = 0; j < i; j++) { + int a = i * resolution + j; + int b = j * resolution + i; + double t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; + } + } + } + }, + FLIP_X { + @Override + public void process(@NonNull double[] arr, int resolution) { + for (int z = 0; z < resolution; z++) { + for (int x = 0, lim = resolution >> 1; x < lim; x++) { + int a = z * resolution + x; + int b = z * resolution + (resolution - x - 1); + double t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; + } + } + } + }, + FLIP_Y { + @Override + public void process(@NonNull double[] arr, int resolution) { + for (int z = 0, lim = resolution >> 1; z < lim; z++) { + for (int x = 0; x < resolution; x++) { + int a = z * resolution + x; + int b = (resolution - z - 1) * resolution + x; + double t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; + } + } + } + }; + + public abstract void process(@NonNull double[] arr, int resolution); +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileMode.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileMode.java new file mode 100644 index 00000000..bd51d564 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileMode.java @@ -0,0 +1,26 @@ +package net.buildtheearth.terraplusplus.dataset.scalar.tile.mode; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; + +/** + * @author DaPorkchop_ + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "name") +@JsonTypeIdResolver(TileMode.TypeIdResolver.class) +@JsonDeserialize +@FunctionalInterface +public interface TileMode { + /** + * @return the path to the tile at the given position + */ + String path(int tileX, int tileZ, int zoom); + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.TILE_MODES); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileModeSimple.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileModeSimple.java new file mode 100644 index 00000000..0108d05a --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileModeSimple.java @@ -0,0 +1,33 @@ +package net.buildtheearth.terraplusplus.dataset.scalar.tile.mode; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.daporkchop.lib.common.misc.string.PStrings; + +/** + * Simple tile format in which tile coordinates are simply used as-is. + * + * @author DaPorkchop_ + */ +@JsonDeserialize +public class TileModeSimple implements TileMode { + protected final String format; + @Getter(onMethod_ = { @JsonGetter }) + protected final String extension; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public TileModeSimple( + @JsonProperty(value = "extension", required = true) @NonNull String extension) { + this.format = "%d/%d/%d." + extension; + this.extension = extension; + } + + @Override + public String path(int tileX, int tileZ, int zoom) { + return PStrings.fastFormat(this.format, zoom, tileX, tileZ); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileModeSlippyMap.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileModeSlippyMap.java new file mode 100644 index 00000000..cfb074e9 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/scalar/tile/mode/TileModeSlippyMap.java @@ -0,0 +1,33 @@ +package net.buildtheearth.terraplusplus.dataset.scalar.tile.mode; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.daporkchop.lib.common.misc.string.PStrings; + +/** + * The OpenStreetMap "Slippy Map" tile format. + * + * @author DaPorkchop_ + */ +@JsonDeserialize +public class TileModeSlippyMap implements TileMode { + protected final String format; + @Getter(onMethod_ = { @JsonGetter }) + protected final String extension; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public TileModeSlippyMap( + @JsonProperty(value = "extension", required = true) @NonNull String extension) { + this.format = "%d/%d/%d." + extension; + this.extension = extension; + } + + @Override + public String path(int tileX, int tileZ, int zoom) { + return PStrings.fastFormat(this.format, zoom, tileX, ((1 << zoom) - 1) - tileZ); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/VectorTiledDataset.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/VectorTiledDataset.java index b54d662d..102f03d4 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/VectorTiledDataset.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/VectorTiledDataset.java @@ -8,6 +8,7 @@ import net.buildtheearth.terraplusplus.projection.EquirectangularProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.BVH; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; import net.minecraft.util.math.ChunkPos; @@ -30,14 +31,14 @@ public VectorTiledDataset(@NonNull IDataset delegate) } @Override - public CompletableFuture> load(@NonNull ChunkPos key) throws Exception { - return this.delegate.getAsync(String.format("tile/%d/%d.json", key.x, key.z)).thenApply(BVH::of); + public CompletableFuture> load(@NonNull TilePos key) throws Exception { + return this.delegate.getAsync(String.format("%d/tile/%d/%d.json", key.zoom(), key.x(), key.z())).thenApply(BVH::of); } @Override - public CompletableFuture[]> getAsync(@NonNull CornerBoundingBox2d bounds) throws OutOfProjectionBoundsException { + public CompletableFuture[]> getAsync(@NonNull CornerBoundingBox2d bounds, int zoom) throws OutOfProjectionBoundsException { Bounds2d localBounds = bounds.fromGeo(this.projection).axisAlign(); - CompletableFuture>[] futures = uncheckedCast(Arrays.stream(localBounds.toTiles(this.tileSize)) + CompletableFuture>[] futures = uncheckedCast(Arrays.stream(localBounds.toTiles(this.tileSize, zoom)) .map(this::getAsync) .toArray(CompletableFuture[]::new)); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/All.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/All.java deleted file mode 100644 index 8d36fd77..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/All.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.generator.CachedChunkData; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(All.Parser.class) -@RequiredArgsConstructor -final class All implements DrawFunction { - @NonNull - protected final DrawFunction[] delegates; - - @Override - public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - for (DrawFunction delegate : this.delegates) { - delegate.drawOnto(data, x, z, weight); - } - } - - static class Parser extends DrawFunctionParser { - @Override - public DrawFunction read(JsonReader in) throws IOException { - return new All(readTypedList(in, this).toArray(new DrawFunction[0])); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java deleted file mode 100644 index f38c5c5a..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.generator.CachedChunkData; -import net.minecraft.block.state.IBlockState; - -import java.io.IOException; - -/** - * {@link DrawFunction} which sets the surface block to a fixed block state. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Block.Parser.class) -@RequiredArgsConstructor -public final class Block implements DrawFunction { - @NonNull - protected final IBlockState state; - - @Override - public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - data.surfaceBlocks()[x * 16 + z] = this.state; - } - - static class Parser extends JsonParser { - @Override - public Block read(JsonReader in) throws IOException { - return new Block(TerraConstants.GSON.fromJson(in, IBlockState.class)); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunction.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunction.java index c4fdc651..81b95e74 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunction.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunction.java @@ -1,7 +1,10 @@ package net.buildtheearth.terraplusplus.dataset.vector.draw; -import com.google.gson.annotations.JsonAdapter; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import lombok.NonNull; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; import net.buildtheearth.terraplusplus.generator.CachedChunkData; /** @@ -9,7 +12,9 @@ * * @author DaPorkchop_ */ -@JsonAdapter(DrawFunctionParser.class) +@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") +@JsonTypeIdResolver(DrawFunction.TypeIdResolver.class) +@JsonDeserialize @FunctionalInterface public interface DrawFunction { /** @@ -21,4 +26,10 @@ public interface DrawFunction { * @param weight the pixel weight */ void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight); + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.VECTOR_DRAW_FUNCTIONS); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionAll.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionAll.java new file mode 100644 index 00000000..d2cf898d --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionAll.java @@ -0,0 +1,31 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DrawFunctionAll implements DrawFunction { + protected final DrawFunction[] children; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DrawFunctionAll( + @JsonProperty(value = "children", required = true) @NonNull DrawFunction[] children) { + this.children = children; + } + + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + for (DrawFunction delegate : this.children) { + delegate.drawOnto(data, x, z, weight); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionBlock.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionBlock.java new file mode 100644 index 00000000..dd29b4da --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionBlock.java @@ -0,0 +1,32 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; +import net.minecraft.block.state.IBlockState; + +/** + * {@link DrawFunction} which sets the surface block to a fixed block state. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DrawFunctionBlock implements DrawFunction { + protected final IBlockState state; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DrawFunctionBlock( + @JsonProperty(value = "state", required = true) @NonNull IBlockState state) { + this.state = state; + } + + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + data.surfaceBlocks()[x * 16 + z] = this.state; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionConditionalRandom.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionConditionalRandom.java new file mode 100644 index 00000000..3c3a42a9 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionConditionalRandom.java @@ -0,0 +1,36 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DrawFunctionConditionalRandom implements DrawFunction { + protected final DrawFunction child; + protected final double chance; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DrawFunctionConditionalRandom( + @JsonProperty(value = "child", required = true) @NonNull DrawFunction child, + @JsonProperty(value = "chance", required = true) double chance) { + this.child = child; + this.chance = chance; + } + + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + if (ThreadLocalRandom.current().nextDouble() <= this.chance) { + this.child.drawOnto(data, x, z, weight); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/NoTrees.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionNoTrees.java similarity index 55% rename from src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/NoTrees.java rename to src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionNoTrees.java index ba819d00..5d2a9e1f 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/NoTrees.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionNoTrees.java @@ -1,21 +1,17 @@ package net.buildtheearth.terraplusplus.dataset.vector.draw; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.generator.EarthGeneratorPipelines; -import java.io.IOException; - /** * {@link DrawFunction} which removes trees at a given position. * * @author DaPorkchop_ */ -@JsonAdapter(NoTrees.Parser.class) -final class NoTrees implements DrawFunction { +@JsonDeserialize +public final class DrawFunctionNoTrees implements DrawFunction { @Override public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { byte[] treeCover = data.getCustom(EarthGeneratorPipelines.KEY_DATA_TREE_COVER, null); @@ -23,13 +19,4 @@ public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int we treeCover[x * 16 + z] = (byte) 0; //set chance to 0 } } - - static class Parser extends JsonParser { - @Override - public NoTrees read(JsonReader in) throws IOException { - in.beginObject(); - in.endObject(); - return new NoTrees(); - } - } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionOcean.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionOcean.java new file mode 100644 index 00000000..94e48cfa --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionOcean.java @@ -0,0 +1,18 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; + +/** + * {@link DrawFunction} which updates the water depth based on the pixel weight. + * + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class DrawFunctionOcean implements DrawFunction { + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + data.updateOceanDepth(x, z, weight); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionParser.java deleted file mode 100644 index 7fe48920..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionParser.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; - -import java.util.Map; - -/** - * @author DaPorkchop_ - */ -public class DrawFunctionParser extends JsonParser.Typed { - public static final Map> TYPES = new Object2ObjectOpenHashMap<>(); - - static { - TYPES.put("all", All.class); - - TYPES.put("weight_add", WeightAdd.class); - TYPES.put("weight_clamp", WeightClamp.class); - TYPES.put("weight_greater_than", WeightGreaterThan.class); - TYPES.put("weight_less_than", WeightLessThan.class); - - TYPES.put("block", Block.class); - TYPES.put("no_trees", NoTrees.class); - TYPES.put("ocean", Ocean.class); - TYPES.put("water", Water.class); - } - - public DrawFunctionParser() { - super("draw", TYPES); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWater.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWater.java new file mode 100644 index 00000000..2c60eaed --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWater.java @@ -0,0 +1,18 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; + +/** + * {@link DrawFunction} which updates the water depth based on the pixel weight. + * + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class DrawFunctionWater implements DrawFunction { + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + data.updateWaterDepth(x, z, weight); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightAdd.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightAdd.java new file mode 100644 index 00000000..c030359f --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightAdd.java @@ -0,0 +1,32 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DrawFunctionWeightAdd implements DrawFunction { + protected final DrawFunction child; + protected final int value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DrawFunctionWeightAdd( + @JsonProperty(value = "child", required = true) @NonNull DrawFunction child, + @JsonProperty(value = "value", required = true) int value) { + this.child = child; + this.value = value; + } + + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + this.child.drawOnto(data, x, z, weight + this.value); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightClamp.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightClamp.java new file mode 100644 index 00000000..4cfc1e46 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightClamp.java @@ -0,0 +1,38 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; +import net.daporkchop.lib.common.math.PMath; + +import static net.daporkchop.lib.common.util.PorkUtil.*; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DrawFunctionWeightClamp implements DrawFunction { + protected final DrawFunction child; + protected final int min; + protected final int max; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DrawFunctionWeightClamp( + @JsonProperty(value = "child", required = true) @NonNull DrawFunction child, + @JsonProperty("min") Integer min, + @JsonProperty("max") Integer max) { + this.child = child; + this.min = fallbackIfNull(min, Integer.MIN_VALUE); + this.max = fallbackIfNull(max, Integer.MAX_VALUE); + } + + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + this.child.drawOnto(data, x, z, PMath.clamp(weight, this.min, this.max)); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightGreaterThan.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightGreaterThan.java new file mode 100644 index 00000000..cb3a27be --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightGreaterThan.java @@ -0,0 +1,34 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DrawFunctionWeightGreaterThan implements DrawFunction { + protected final DrawFunction child; + protected final int value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DrawFunctionWeightGreaterThan( + @JsonProperty(value = "child", required = true) @NonNull DrawFunction child, + @JsonProperty(value = "value", required = true) int value) { + this.child = child; + this.value = value; + } + + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + if (weight > this.value) { + this.child.drawOnto(data, x, z, weight); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightLessThan.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightLessThan.java new file mode 100644 index 00000000..d8db1a8d --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionWeightLessThan.java @@ -0,0 +1,34 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DrawFunctionWeightLessThan implements DrawFunction { + protected final DrawFunction child; + protected final int value; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DrawFunctionWeightLessThan( + @JsonProperty(value = "child", required = true) @NonNull DrawFunction child, + @JsonProperty(value = "value", required = true) int value) { + this.child = child; + this.value = value; + } + + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + if (weight < this.value) { + this.child.drawOnto(data, x, z, weight); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Ocean.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Ocean.java deleted file mode 100644 index 1ec3cf7e..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Ocean.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.generator.CachedChunkData; - -import java.io.IOException; - -/** - * {@link DrawFunction} which updates the water depth based on the pixel weight. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Ocean.Parser.class) -final class Ocean implements DrawFunction { - @Override - public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - data.updateOceanDepth(x, z, weight); - } - - static class Parser extends JsonParser { - @Override - public Ocean read(JsonReader in) throws IOException { - in.beginObject(); - in.endObject(); - return new Ocean(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Water.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Water.java deleted file mode 100644 index 6eb07e25..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Water.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.generator.CachedChunkData; - -import java.io.IOException; - -/** - * {@link DrawFunction} which updates the water depth based on the pixel weight. - * - * @author DaPorkchop_ - */ -@JsonAdapter(Water.Parser.class) -final class Water implements DrawFunction { - @Override - public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - data.updateWaterDepth(x, z, weight); - } - - static class Parser extends JsonParser { - @Override - public Water read(JsonReader in) throws IOException { - in.beginObject(); - in.endObject(); - return new Water(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightAdd.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightAdd.java deleted file mode 100644 index 5261bc24..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightAdd.java +++ /dev/null @@ -1,55 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.generator.CachedChunkData; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(WeightAdd.Parser.class) -@Builder -final class WeightAdd implements DrawFunction { - @NonNull - protected final DrawFunction delegate; - protected final int value; - - @Override - public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - this.delegate.drawOnto(data, x, z, weight + this.value); - } - - static class Parser extends JsonParser { - @Override - public WeightAdd read(JsonReader in) throws IOException { - WeightAddBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "value": - builder.value(in.nextInt()); - break; - case "delegate": - in.beginObject(); - builder.delegate(TerraConstants.GSON.fromJson(in, DrawFunction.class)); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightClamp.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightClamp.java deleted file mode 100644 index 903d6fdf..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightClamp.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.generator.CachedChunkData; -import net.daporkchop.lib.common.math.PMath; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(WeightClamp.Parser.class) -@Builder -final class WeightClamp implements DrawFunction { - @NonNull - protected final DrawFunction delegate; - @Builder.Default - protected final int min = Integer.MIN_VALUE; - @Builder.Default - protected final int max = Integer.MAX_VALUE; - - @Override - public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - this.delegate.drawOnto(data, x, z, PMath.clamp(weight, this.min, this.max)); - } - - static class Parser extends JsonParser { - @Override - public WeightClamp read(JsonReader in) throws IOException { - WeightClampBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "min": - builder.min(in.nextInt()); - break; - case "max": - builder.max(in.nextInt()); - break; - case "delegate": - in.beginObject(); - builder.delegate(TerraConstants.GSON.fromJson(in, DrawFunction.class)); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightGreaterThan.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightGreaterThan.java deleted file mode 100644 index 275b9e59..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightGreaterThan.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.generator.CachedChunkData; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(WeightGreaterThan.Parser.class) -@Builder -final class WeightGreaterThan implements DrawFunction { - @NonNull - protected final DrawFunction delegate; - protected final int value; - - @Override - public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - if (weight > this.value) { - this.delegate.drawOnto(data, x, z, weight); - } - } - - static class Parser extends JsonParser { - @Override - public WeightGreaterThan read(JsonReader in) throws IOException { - WeightGreaterThanBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "value": - builder.value(in.nextInt()); - break; - case "delegate": - in.beginObject(); - builder.delegate(TerraConstants.GSON.fromJson(in, DrawFunction.class)); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightLessThan.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightLessThan.java deleted file mode 100644 index 93530dfe..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/WeightLessThan.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.buildtheearth.terraplusplus.dataset.vector.draw; - -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import lombok.Builder; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import net.buildtheearth.terraplusplus.generator.CachedChunkData; - -import java.io.IOException; - -/** - * @author DaPorkchop_ - */ -@JsonAdapter(WeightLessThan.Parser.class) -@Builder -final class WeightLessThan implements DrawFunction { - @NonNull - protected final DrawFunction delegate; - protected final int value; - - @Override - public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - if (weight < this.value) { - this.delegate.drawOnto(data, x, z, weight); - } - } - - static class Parser extends JsonParser { - @Override - public WeightLessThan read(JsonReader in) throws IOException { - WeightLessThanBuilder builder = builder(); - - in.beginObject(); - while (in.peek() != JsonToken.END_OBJECT) { - String name = in.nextName(); - switch (name) { - case "value": - builder.value(in.nextInt()); - break; - case "delegate": - in.beginObject(); - builder.delegate(TerraConstants.GSON.fromJson(in, DrawFunction.class)); - in.endObject(); - break; - default: - throw new IllegalStateException("invalid property: " + name); - } - } - in.endObject(); - - return builder.build(); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/AbstractVectorGeometry.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/AbstractVectorGeometry.java index 005d04d4..d494a4bf 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/AbstractVectorGeometry.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/AbstractVectorGeometry.java @@ -7,6 +7,7 @@ import net.buildtheearth.terraplusplus.dataset.geojson.geometry.LineString; import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Point; import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; import java.util.List; @@ -32,4 +33,9 @@ protected static void convertToSegments(@NonNull LineString line, @NonNull List< protected final double layer; @NonNull protected final DrawFunction draw; + protected final IntRange levels; + + protected boolean containsZoom(int zoom) { + return this.levels == null || this.levels.contains(zoom); + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/VectorGeometry.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/VectorGeometry.java index 7aec3c95..97e8e709 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/VectorGeometry.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/VectorGeometry.java @@ -11,14 +11,15 @@ */ public interface VectorGeometry extends Comparable, Bounds2d { /** - * Modifies the given {@link CachedChunkData.Builder} for the given chunk + * Modifies the given {@link CachedChunkData.Builder} for the given tile. * * @param builder the {@link CachedChunkData.Builder} - * @param chunkX the chunk's X coordinate - * @param chunkZ the chunk's Z coordinate - * @param bounds the chunk's bounding box + * @param tileX the tile's X coordinate + * @param tileZ the tile's Z coordinate + * @param zoom the tile's zoom level + * @param bounds the tile's bounding box */ - void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chunkZ, @NonNull Bounds2d bounds); + void apply(@NonNull CachedChunkData.Builder builder, int tileX, int tileZ, int zoom, @NonNull Bounds2d bounds); /** * @return this element's id diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/AbstractLine.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/AbstractLine.java index 1dea7eba..9f848eb8 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/AbstractLine.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/AbstractLine.java @@ -8,6 +8,7 @@ import net.buildtheearth.terraplusplus.dataset.vector.geometry.AbstractVectorGeometry; import net.buildtheearth.terraplusplus.dataset.vector.geometry.Segment; import net.buildtheearth.terraplusplus.util.bvh.BVH; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; import java.util.ArrayList; import java.util.List; @@ -19,8 +20,8 @@ public abstract class AbstractLine extends AbstractVectorGeometry { protected final BVH segments; - public AbstractLine(@NonNull String id, double layer, @NonNull DrawFunction draw, @NonNull MultiLineString lines) { - super(id, layer, draw); + public AbstractLine(@NonNull String id, double layer, @NonNull DrawFunction draw, IntRange levels, @NonNull MultiLineString lines) { + super(id, layer, draw, levels); List segments = new ArrayList<>(); for (LineString line : lines.lines()) { //convert MultiLineString to line segments diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/NarrowLine.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/NarrowLine.java index 503653dd..a2990338 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/NarrowLine.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/NarrowLine.java @@ -6,6 +6,7 @@ import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; import static java.lang.Math.*; import static net.daporkchop.lib.common.math.PMath.*; @@ -13,22 +14,34 @@ /** * @author DaPorkchop_ */ -public final class NarrowLine extends AbstractLine { - public NarrowLine(@NonNull String id, double layer, @NonNull DrawFunction draw, @NonNull MultiLineString lines) { - super(id, layer, draw, lines); +public class NarrowLine extends AbstractLine { + protected final int weight; + + public NarrowLine(@NonNull String id, double layer, @NonNull DrawFunction draw, IntRange levels, @NonNull MultiLineString lines, int weight) { + super(id, layer, draw, levels, lines); + + this.weight = weight; } @Override - public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chunkZ, @NonNull Bounds2d bounds) { + public void apply(@NonNull CachedChunkData.Builder builder, int tileX, int tileZ, int zoom, @NonNull Bounds2d bounds) { + if (!this.containsZoom(zoom)) { + return; + } + + int baseX = Coords.cubeToMinBlock(tileX); + int baseZ = Coords.cubeToMinBlock(tileZ); + double scale = 1.0d / (1 << zoom); + this.segments.forEachIntersecting(bounds, s -> { - double x0 = s.x0(); - double x1 = s.x1(); - double z0 = s.z0(); - double z1 = s.z1(); + double x0 = s.x0() * scale; + double x1 = s.x1() * scale; + double z0 = s.z0() * scale; + double z1 = s.z1() * scale; - //slope must not be infinity, slight inaccuracy shouldn't even be noticible unless you go looking for it + //slope must not be infinity, slight inaccuracy shouldn't even be noticeable unless you go looking for it double dif = x1 - x0; - double slope = (z1 - z0) / ((abs(dif) < 0.01d ? x1 + copySign(0.01d, dif) : x1) - x0); + double slope = (z1 - z0) / ((abs(dif) < 0.01d * scale ? x1 + copySign(0.01d * scale, dif) : x1) - x0); double offset = z0 - slope * x0; if (x0 > x1) { @@ -37,15 +50,15 @@ public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chun x1 = tmp; } - int sx = max(floorI(x0) - Coords.cubeToMinBlock(chunkX), 0); - int ex = min(floorI(x1) - Coords.cubeToMinBlock(chunkX), 15); + int sx = max(floorI(x0) - baseX, 0); + int ex = min(floorI(x1) - baseX, 15); for (int x = max(sx, 0); x <= ex; x++) { - double realx = max(x + Coords.cubeToMinBlock(chunkX), x0); + double realx = max(x + baseX, x0); double nextx = min(realx + 1.0d, x1); - int from = floorI((slope * realx + offset)) - Coords.cubeToMinBlock(chunkZ); - int to = floorI((slope * nextx + offset)) - Coords.cubeToMinBlock(chunkZ); + int from = floorI((slope * realx + offset)) - baseZ; + int to = floorI((slope * nextx + offset)) - baseZ; if (from > to) { int tmp = from; @@ -56,7 +69,7 @@ public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chun to = min(to, 15); for (int z = max(0, from); z <= to; z++) { - this.draw.drawOnto(builder, x, z, 1); + this.draw.drawOnto(builder, x, z, this.weight); } } }); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/WideLine.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/WideLine.java index 4a74e3d7..91f06318 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/WideLine.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/line/WideLine.java @@ -7,6 +7,7 @@ import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; import net.minecraft.util.math.MathHelper; import static java.lang.Math.*; @@ -15,34 +16,47 @@ /** * @author DaPorkchop_ */ -public final class WideLine extends AbstractLine { +public class WideLine extends NarrowLine { protected final double radius; - public WideLine(@NonNull String id, double layer, @NonNull DrawFunction draw, @NonNull MultiLineString lines, double radius) { - super(id, layer, draw, lines); + public WideLine(@NonNull String id, double layer, @NonNull DrawFunction draw, IntRange levels, @NonNull MultiLineString lines, double radius) { + super(id, layer, draw, levels, lines, floorI(radius)); this.radius = radius; } @Override - public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chunkZ, @NonNull Bounds2d bounds) { + public void apply(@NonNull CachedChunkData.Builder builder, int tileX, int tileZ, int zoom, @NonNull Bounds2d bounds) { + if (!this.containsZoom(zoom)) { + return; + } + + if (this.radius <= 1 << zoom) { + super.apply(builder, tileX, tileZ, zoom, bounds); + return; + } + + int baseX = Coords.cubeToMinBlock(tileX << zoom); + int baseZ = Coords.cubeToMinBlock(tileZ << zoom); + int step = 1 << zoom; + this.segments.forEachIntersecting(bounds.expand(this.radius), s -> { double radius = this.radius; double radiusSq = radius * radius; - double lon0 = s.x0() - Coords.cubeToMinBlock(chunkX); - double lon1 = s.x1() - Coords.cubeToMinBlock(chunkX); - double lat0 = s.z0() - Coords.cubeToMinBlock(chunkZ); - double lat1 = s.z1() - Coords.cubeToMinBlock(chunkZ); + double lon0 = s.x0() - baseX; + double lon1 = s.x1() - baseX; + double lat0 = s.z0() - baseZ; + double lat1 = s.z1() - baseZ; int minX = max((int) floor(min(lon0, lon1) - radius), 0); - int maxX = min((int) ceil(max(lon0, lon1) + radius), 16); + int maxX = min((int) ceil(max(lon0, lon1) + radius), 16 << zoom); int minZ = max((int) floor(min(lat0, lat1) - radius), 0); - int maxZ = min((int) ceil(max(lat0, lat1) + radius), 16); + int maxZ = min((int) ceil(max(lat0, lat1) + radius), 16 << zoom); double segmentLengthSq = (lon1 - lon0) * (lon1 - lon0) + (lat1 - lat0) * (lat1 - lat0); - for (int x = minX; x < maxX; x++) { - for (int z = minZ; z < maxZ; z++) { + for (int x = minX; x < maxX; x += step) { + for (int z = minZ; z < maxZ; z += step) { double r = ((x - lon0) * (lon1 - lon0) + (z - lat0) * (lat1 - lat0)) / segmentLengthSq; r = MathHelper.clamp(r, 0.0d, 1.0d); @@ -50,7 +64,7 @@ public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chun double dz = MathUtil.lerp(r, lat0, lat1) - z; double dSq = dx * dx + dz * dz; if (dSq < radiusSq) { - this.draw.drawOnto(builder, x, z, floorI(radius - sqrt(dSq))); + this.draw.drawOnto(builder, x >> zoom, z >> zoom, floorI(radius - sqrt(dSq))); } } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/AbstractPolygon.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/AbstractPolygon.java index 779ab671..510d95db 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/AbstractPolygon.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/AbstractPolygon.java @@ -2,7 +2,7 @@ import lombok.Getter; import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.dataset.geojson.geometry.LineString; import net.buildtheearth.terraplusplus.dataset.geojson.geometry.MultiPolygon; import net.buildtheearth.terraplusplus.dataset.geojson.geometry.Point; @@ -11,6 +11,7 @@ import net.buildtheearth.terraplusplus.dataset.vector.geometry.AbstractVectorGeometry; import net.buildtheearth.terraplusplus.dataset.vector.geometry.Segment; import net.buildtheearth.terraplusplus.util.interval.IntervalTree; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; import java.util.ArrayList; import java.util.Arrays; @@ -33,8 +34,8 @@ public abstract class AbstractPolygon extends AbstractVectorGeometry { protected final double minZ; protected final double maxZ; - public AbstractPolygon(@NonNull String id, double layer, @NonNull DrawFunction draw, @NonNull MultiPolygon polygons) { - super(id, layer, draw); + public AbstractPolygon(@NonNull String id, double layer, @NonNull DrawFunction draw, IntRange levels, @NonNull MultiPolygon polygons) { + super(id, layer, draw, levels); //compute bounds and convert multipolygon to line segments double minX = Double.POSITIVE_INFINITY; diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/DistancePolygon.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/DistancePolygon.java index 3b724b73..79dc1a7c 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/DistancePolygon.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/DistancePolygon.java @@ -6,6 +6,7 @@ import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; import java.util.Arrays; @@ -14,21 +15,28 @@ import static net.daporkchop.lib.common.util.PValidation.*; /** + * A custom polygon rasterizer which not only fills the polygon area, but computes the distance from the edge of the polygon - both inside and out - up to a given maximum + * distance. + * * @author DaPorkchop_ */ public final class DistancePolygon extends AbstractPolygon { protected final int maxDist; - public DistancePolygon(@NonNull String id, double layer, @NonNull DrawFunction draw, @NonNull MultiPolygon polygons, int maxDist) { - super(id, layer, draw, polygons); + public DistancePolygon(@NonNull String id, double layer, @NonNull DrawFunction draw, IntRange levels, @NonNull MultiPolygon polygons, int maxDist) { + super(id, layer, draw, levels, polygons); this.maxDist = positive(maxDist, "maxDist"); } @Override - public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chunkZ, @NonNull Bounds2d bounds) { - int baseX = Coords.cubeToMinBlock(chunkX); - int baseZ = Coords.cubeToMinBlock(chunkZ); + public void apply(@NonNull CachedChunkData.Builder builder, int tileX, int tileZ, int zoom, @NonNull Bounds2d bounds) { + if (!this.containsZoom(zoom)) { + return; + } + + int baseX = Coords.cubeToMinBlock(tileX << zoom); + int baseZ = Coords.cubeToMinBlock(tileZ << zoom); int maxDist = this.maxDist; int[][] distances2d = new int[(maxDist << 1) + 1][16]; @@ -39,7 +47,7 @@ public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chun int[] distances = distances2d[0]; System.arraycopy(distances2d, 1, distances2d, 0, maxDist << 1); //shift distances down by one distances2d[maxDist << 1] = distances; - double[] intersectionPoints = this.getIntersectionPoints(x + baseX); + double[] intersectionPoints = this.getIntersectionPoints((x << zoom) + baseX); if (intersectionPoints.length == 0) { //no intersections along this line Arrays.fill(distances, Integer.MIN_VALUE); @@ -54,7 +62,8 @@ public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chun if (i < intersectionPoints.length) { //index is valid int mask = 0; - int min = floorI(intersectionPoints[i++]) - baseZ; + int min = (floorI(intersectionPoints[i++]) - baseZ) >> zoom; + int limit = baseX + (16 << zoom); //fill everything up to this point with blanks for (int z = 0, itrMax = clamp(min, 0, 16); z < itrMax; z++) { @@ -62,7 +71,7 @@ public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chun } do { - max = floorI(intersectionPoints[i++]) - baseZ; + max = (floorI(intersectionPoints[i++]) - baseZ) >> zoom; for (int z = clamp(min, 0, 16), itrMax = clamp(max, 0, 16); z < itrMax; z++) { distances[z] = min(z - min, max - z - 1) ^ mask; @@ -70,9 +79,9 @@ public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chun min = max; mask = ~mask; - } while (i < intersectionPoints.length && max <= baseX + 16); + } while (i < intersectionPoints.length && max <= limit); } else { //index is too high, simply fill from the end - max = floorI(intersectionPoints[intersectionPoints.length - 1]) - baseZ; + max = (floorI(intersectionPoints[intersectionPoints.length - 1]) - baseZ) >> zoom; } //fill everything to the edge with blanks @@ -116,4 +125,24 @@ public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chun } } } + + @Override + public double minX() { + return super.minX() - this.maxDist; + } + + @Override + public double maxX() { + return super.maxX() + this.maxDist; + } + + @Override + public double minZ() { + return super.minZ() - this.maxDist; + } + + @Override + public double maxZ() { + return super.maxZ() + this.maxDist; + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/FillPolygon.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/FillPolygon.java index d1f5bbc2..5ab634f7 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/FillPolygon.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/geometry/polygon/FillPolygon.java @@ -6,6 +6,7 @@ import net.buildtheearth.terraplusplus.dataset.vector.draw.DrawFunction; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.jackson.IntRange; import static net.daporkchop.lib.common.math.PMath.*; @@ -13,21 +14,25 @@ * @author DaPorkchop_ */ public final class FillPolygon extends AbstractPolygon { - public FillPolygon(@NonNull String id, double layer, @NonNull DrawFunction draw, @NonNull MultiPolygon polygons) { - super(id, layer, draw, polygons); + public FillPolygon(@NonNull String id, double layer, @NonNull DrawFunction draw, IntRange levels, @NonNull MultiPolygon polygons) { + super(id, layer, draw, levels, polygons); } @Override - public void apply(@NonNull CachedChunkData.Builder builder, int chunkX, int chunkZ, @NonNull Bounds2d bounds) { - int baseX = Coords.cubeToMinBlock(chunkX); - int baseZ = Coords.cubeToMinBlock(chunkZ); + public void apply(@NonNull CachedChunkData.Builder builder, int tileX, int tileZ, int zoom, @NonNull Bounds2d bounds) { + if (!this.containsZoom(zoom)) { + return; + } + + int baseX = Coords.cubeToMinBlock(tileX << zoom); + int baseZ = Coords.cubeToMinBlock(tileZ << zoom); for (int x = 0; x < 16; x++) { - double[] intersectionPoints = this.getIntersectionPoints(x + baseX); + double[] intersectionPoints = this.getIntersectionPoints((x << zoom) + baseX); for (int i = 0; i < intersectionPoints.length; ) { - int min = clamp(floorI(intersectionPoints[i++]) - baseZ, 0, 16); - int max = clamp(floorI(intersectionPoints[i++]) - baseZ, 0, 16); + int min = clamp((floorI(intersectionPoints[i++]) - baseZ) >> zoom, 0, 16); + int max = clamp((floorI(intersectionPoints[i++]) - baseZ) >> zoom, 0, 16); for (int z = min; z < max; z++) { this.draw.drawOnto(builder, x, z, 1); } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java index e29621dd..573f873f 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java @@ -8,8 +8,8 @@ import lombok.Setter; import net.buildtheearth.terraplusplus.util.CustomAttributeContainer; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; -import net.daporkchop.lib.common.ref.Ref; -import net.daporkchop.lib.common.ref.ThreadRef; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; import net.daporkchop.lib.common.util.PorkUtil; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Biomes; @@ -36,7 +36,7 @@ public class CachedChunkData extends CustomAttributeContainer { public static final int WATERDEPTH_TYPE_WATER = (byte) 0x00; public static final int WATERDEPTH_TYPE_OCEAN = (byte) 0x40; - private static final Ref BUILDER_CACHE = ThreadRef.soft(Builder::new); + private static final Cached BUILDER_CACHE = Cached.threadLocal(Builder::new, ReferenceStrength.SOFT); public static Builder builder() { return BUILDER_CACHE.get().reset(); diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java b/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java index 1208999d..a14965b2 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java @@ -2,8 +2,8 @@ import lombok.Getter; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; -import net.daporkchop.lib.common.ref.Ref; -import net.daporkchop.lib.common.ref.ThreadRef; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; import net.minecraft.world.biome.Biome; import java.util.Arrays; @@ -15,7 +15,7 @@ */ @Getter public class ChunkBiomesBuilder implements IEarthAsyncDataBuilder> { - private static final Ref BUILDER_CACHE = ThreadRef.soft(ChunkBiomesBuilder::new); + private static final Cached BUILDER_CACHE = Cached.threadLocal(ChunkBiomesBuilder::new, ReferenceStrength.SOFT); public static ChunkBiomesBuilder get() { return BUILDER_CACHE.get().reset(); diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthBiomeProvider.java b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthBiomeProvider.java index 7d6b2b42..90a2b1b0 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthBiomeProvider.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthBiomeProvider.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import net.buildtheearth.terraplusplus.generator.biome.IEarthBiomeFilter; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; +import net.buildtheearth.terraplusplus.util.TilePos; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.biome.Biome; @@ -19,7 +20,7 @@ @RequiredArgsConstructor public class EarthBiomeProvider extends BiomeProvider { - protected final LoadingCache>> cache; + protected final LoadingCache>> cache; public EarthBiomeProvider(@NonNull EarthGeneratorSettings settings) { this.cache = CacheBuilder.newBuilder() @@ -36,12 +37,28 @@ public ImmutableCompactArray getBiomesForChunk(ChunkPos pos) { } /** - * Gets the biomes in the given chunk. - * - * @param pos the position of the chunk - * @return a {@link CompletableFuture} which will be completed with the biomes in the chunk + * @deprecated use {@link #getBiomesForTileAsync(TilePos)} */ + @Deprecated public CompletableFuture> getBiomesForChunkAsync(ChunkPos pos) { + return this.getBiomesForTileAsync(new TilePos(pos.x, pos.z, 0)); + } + + /** + * @deprecated this method is blocking, use {@link #getBiomesForTileAsync(TilePos)} + */ + @Deprecated + public ImmutableCompactArray getBiomesForTile(TilePos pos) { + return this.getBiomesForTileAsync(pos).join(); + } + + /** + * Gets the biomes in the given tile. + * + * @param pos the position of the tile + * @return a {@link CompletableFuture} which will be completed with the biomes in the tile + */ + public CompletableFuture> getBiomesForTileAsync(TilePos pos) { return this.cache.getUnchecked(pos); } @@ -131,7 +148,7 @@ public BlockPos findBiomePosition(int x, int z, int range, List biomes, R * * @author DaPorkchop_ */ - public static class ChunkDataLoader extends CacheLoader>> { + public static class ChunkDataLoader extends CacheLoader>> { protected final GeneratorDatasets datasets; protected final IEarthBiomeFilter[] filters; @@ -141,7 +158,7 @@ public ChunkDataLoader(@NonNull EarthGeneratorSettings settings) { } @Override - public CompletableFuture> load(@NonNull ChunkPos pos) { + public CompletableFuture> load(@NonNull TilePos pos) { return IEarthAsyncPipelineStep.getFuture(pos, this.datasets, this.filters, ChunkBiomesBuilder::get); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java index 7a99b2b7..9d0b500d 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java @@ -27,18 +27,18 @@ import io.github.opencubicchunks.cubicchunks.cubicgen.customcubic.structure.CubicRavineGenerator; import io.github.opencubicchunks.cubicchunks.cubicgen.customcubic.structure.feature.CubicStrongholdGenerator; import lombok.NonNull; -import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.TerraMod; import net.buildtheearth.terraplusplus.generator.data.IEarthDataBaker; import net.buildtheearth.terraplusplus.generator.populate.IEarthPopulator; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.TerraConstants; +import net.buildtheearth.terraplusplus.util.TilePos; import net.minecraft.block.state.IBlockState; -import net.minecraft.init.Blocks; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; import net.minecraft.util.text.TextComponentString; import net.minecraft.world.World; +import net.minecraft.world.WorldServer; import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.BiomeProvider; import net.minecraft.world.chunk.Chunk; @@ -59,6 +59,7 @@ import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import static java.lang.Math.*; @@ -70,8 +71,8 @@ public class EarthGenerator extends BasicCubeGenerator { String asyncVersion = "1.12.2-0.0.1175.0"; //the version at which async terrain gen was added if (cubicchunks != null && asyncVersion.compareTo(TerraConstants.CC_VERSION) <= 0) { //async terrain is supported on this version! register async generation callbacks - CubeGeneratorsRegistry.registerColumnAsyncLoadingCallback((world, data) -> asyncCallback(world, data.getPos())); - CubeGeneratorsRegistry.registerCubeAsyncLoadingCallback((world, data) -> asyncCallback(world, data.getPos().chunkPos())); + CubeGeneratorsRegistry.registerColumnAsyncLoadingCallback((world, data) -> asyncCallback(world, new TilePos(data.getPos()))); + CubeGeneratorsRegistry.registerCubeAsyncLoadingCallback((world, data) -> asyncCallback(world, new TilePos(data.getPos()))); } else { //we're on an older version of CC that doesn't support async terrain TerraMod.LOGGER.error("Async terrain not available!"); @@ -94,7 +95,7 @@ public void onPlayerLogIn(PlayerEvent.PlayerLoggedInEvent event) { } } - private static void asyncCallback(World world, ChunkPos pos) { + private static void asyncCallback(World world, TilePos pos) { ICubicWorldServer cubicWorld; if (world instanceof ICubicWorld && (cubicWorld = (ICubicWorldServer) world).isCubicWorld()) { //ignore vanilla worlds ICubeGenerator cubeGenerator = cubicWorld.getCubeGenerator(); @@ -105,10 +106,6 @@ private static void asyncCallback(World world, ChunkPos pos) { } } - public static boolean isNullIsland(int chunkX, int chunkZ) { - return max(chunkX ^ (chunkX >> 31), chunkZ ^ (chunkZ >> 31)) < 3; - } - public final EarthGeneratorSettings settings; public final BiomeProvider biomes; public final GeographicProjection projection; @@ -122,12 +119,12 @@ public static boolean isNullIsland(int chunkX, int chunkZ) { public final GeneratorDatasets datasets; - public final LoadingCache> cache; + public final LoadingCache> cache; public EarthGenerator(World world) { super(world); - this.settings = EarthGeneratorSettings.parse(world.getWorldInfo().getGeneratorOptions()); + this.settings = EarthGeneratorSettings.forWorld((WorldServer) world); this.cubiccfg = this.settings.customCubic(); this.projection = this.settings.projection(); @@ -184,7 +181,7 @@ public boolean supportsConcurrentColumnGeneration() { @Override public GeneratorReadyState pollAsyncColumnGenerator(int chunkX, int chunkZ) { - CompletableFuture future = this.cache.getUnchecked(new ChunkPos(chunkX, chunkZ)); + CompletableFuture future = this.cache.getUnchecked(new TilePos(chunkX, chunkZ, 0)); if (!future.isDone()) { return GeneratorReadyState.WAITING; } else if (future.isCompletedExceptionally()) { @@ -196,13 +193,13 @@ public GeneratorReadyState pollAsyncColumnGenerator(int chunkX, int chunkZ) { @Override public void generateColumn(Chunk column) { //legacy compat method - CachedChunkData data = this.cache.getUnchecked(column.getPos()).join(); + CachedChunkData data = this.cache.getUnchecked(new TilePos(column.x, column.z, 0)).join(); this.generateColumn(column, data); } @Override public Optional tryGenerateColumn(World world, int columnX, int columnZ, ChunkPrimer primer, boolean forceGenerate) { - CompletableFuture future = this.cache.getUnchecked(new ChunkPos(columnX, columnZ)); + CompletableFuture future = this.cache.getUnchecked(new TilePos(columnX, columnZ, 0)); if (!forceGenerate && (!future.isDone() || future.isCompletedExceptionally())) { return Optional.empty(); } @@ -222,17 +219,17 @@ public boolean supportsConcurrentCubeGeneration() { } @Deprecated - @Override + @Override public CubePrimer generateCube(int cubeX, int cubeY, int cubeZ) { //legacy compat method CubePrimer primer = new CubePrimer(); - CachedChunkData data = this.cache.getUnchecked(new ChunkPos(cubeX, cubeZ)).join(); + CachedChunkData data = this.cache.getUnchecked(new TilePos(cubeX, cubeZ, 0)).join(); this.generateCube(cubeX, cubeY, cubeZ, primer, data); return primer; } @Override public CubePrimer generateCube(int cubeX, int cubeY, int cubeZ, CubePrimer primer) { //legacy compat method - CachedChunkData data = this.cache.getUnchecked(new ChunkPos(cubeX, cubeZ)).join(); + CachedChunkData data = this.cache.getUnchecked(new TilePos(cubeX, cubeZ, 0)).join(); this.generateCube(cubeX, cubeY, cubeZ, primer, data); return primer; } @@ -244,7 +241,7 @@ public GeneratorReadyState pollAsyncCubeGenerator(int cubeX, int cubeY, int cube @Override public Optional tryGenerateCube(int cubeX, int cubeY, int cubeZ, CubePrimer primer, boolean forceGenerate) { - CompletableFuture future = this.cache.getUnchecked(new ChunkPos(cubeX, cubeZ)); + CompletableFuture future = this.cache.getUnchecked(new TilePos(cubeX, cubeZ, 0)); if (!forceGenerate && (!future.isDone() || future.isCompletedExceptionally())) { return Optional.empty(); } @@ -275,23 +272,21 @@ protected void generateCube(int cubeX, int cubeY, int cubeZ, CubePrimer primer, } protected void generateSurface(int cubeX, int cubeY, int cubeZ, CubePrimer primer, CachedChunkData data) { - IBlockState stone = Blocks.STONE.getDefaultState(); - IBlockState water = Blocks.WATER.getDefaultState(); + Supplier fill = this.settings.terrainSettings().fill(); + Supplier water = this.settings.terrainSettings().water(); + Supplier surface = this.settings.terrainSettings().surface(); + Supplier top = this.settings.terrainSettings().top(); + if (data.belowSurface(cubeY + 2)) { //below surface -> solid stone (padding of 2 cubes because some replacers might need it) - //technically, i could reflectively get access to the primer's underlying char[] and use Arrays.fill(), because this - // implementation causes 4096 calls to ObjectIntIdentityMap#get() when only 1 would be necessary... for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { - primer.setBlockState(x, y, z, stone); + primer.setBlockState(x, y, z, fill.get()); } } } } else if (data.aboveSurface(cubeY)) { //above surface -> air (no padding here, replacers don't normally affect anything above the surface) } else { - IBlockState grass = Blocks.GRASS.getDefaultState(); - IBlockState dirt = Blocks.DIRT.getDefaultState(); - for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { int groundHeight = data.groundHeight(x, z); @@ -306,31 +301,46 @@ protected void generateSurface(int cubeX, int cubeY, int cubeZ, CubePrimer prime int groundTopInCube = min(groundTop, 15); int waterTop = min(waterHeight - Coords.cubeToMinBlock(cubeY), 15); - int blockX = Coords.cubeToMinBlock(cubeX) + x; - int blockZ = Coords.cubeToMinBlock(cubeZ) + z; - IBiomeBlockReplacer[] replacers = this.biomeBlockReplacers[data.biome(x, z) & 0xFF]; - for (int y = 0; y <= groundTopInCube; y++) { - int blockY = Coords.cubeToMinBlock(cubeY) + y; - double density = groundTop - y; - IBlockState state = stone; - for (IBiomeBlockReplacer replacer : replacers) { - state = replacer.getReplacedBlock(state, blockX, blockY, blockZ, dx, -1.0d, dz, density); + if (this.settings.terrainSettings().useCwgReplacers()) { + int blockX = Coords.cubeToMinBlock(cubeX) + x; + int blockZ = Coords.cubeToMinBlock(cubeZ) + z; + IBiomeBlockReplacer[] replacers = this.biomeBlockReplacers[data.biome(x, z) & 0xFF]; + for (int y = 0; y <= groundTopInCube; y++) { + int blockY = Coords.cubeToMinBlock(cubeY) + y; + double density = groundTop - y; + IBlockState state = fill.get(); + for (IBiomeBlockReplacer replacer : replacers) { + state = replacer.getReplacedBlock(state, blockX, blockY, blockZ, dx, -1.0d, dz, density); + } + + //calling this explicitly increases the likelihood of JIT inlining it + //(for reference: previously, CliffReplacer was manually added to each biome as the last replacer) + state = CliffReplacer.INSTANCE.getReplacedBlock(state, blockX, blockY, blockZ, dx, -1.0d, dz, density); + + if (groundHeight < waterHeight && state == top.get()) { //hacky workaround for underwater grass + state = surface.get(); + } + + primer.setBlockState(x, y, z, state); } - - //calling this explicitly increases the likelihood of JIT inlining it - //(for reference: previously, CliffReplacer was manually added to each biome as the last replacer) - state = CliffReplacer.INSTANCE.getReplacedBlock(state, blockX, blockY, blockZ, dx, -1.0d, dz, density); - - if (groundHeight < waterHeight && state == grass) { //hacky workaround for underwater grass - state = dirt; + } else { + for (int y = 0; y <= groundTopInCube; y++) { + IBlockState state; + if (y == groundTop) { + state = top.get(); + } else if (y + 5 >= groundTop) { + state = surface.get(); + } else { + state = fill.get(); + } + + primer.setBlockState(x, y, z, state); } - - primer.setBlockState(x, y, z, state); } //fill water for (int y = max(groundTopInCube + 1, 0); y <= waterTop; y++) { - primer.setBlockState(x, y, z, water); + primer.setBlockState(x, y, z, water.get()); } } } @@ -343,7 +353,7 @@ public GeneratorReadyState pollAsyncCubePopulator(int cubeX, int cubeY, int cube // checking all neighbors here improves performance when checking if a cube can be generated for (int dx = -1; dx <= 1; dx++) { for (int dz = -1; dz <= 1; dz++) { - CompletableFuture future = this.cache.getUnchecked(new ChunkPos(cubeX + dx, cubeZ + dz)); + CompletableFuture future = this.cache.getUnchecked(new TilePos(cubeX + dx, cubeZ + dz, 0)); if (!future.isDone()) { return GeneratorReadyState.WAITING; } else if (future.isCompletedExceptionally()) { @@ -364,7 +374,7 @@ public void populate(ICube cube) { CachedChunkData[] datas = new CachedChunkData[2 * 2]; for (int i = 0, dx = 0; dx < 2; dx++) { for (int dz = 0; dz < 2; dz++) { - datas[i++] = this.cache.getUnchecked(new ChunkPos(cube.getX() + dx, cube.getZ() + dz)).join(); + datas[i++] = this.cache.getUnchecked(new TilePos(cube.getX() + dx, cube.getZ() + dz, 0)).join(); } } @@ -374,7 +384,7 @@ public void populate(ICube cube) { this.cubiccfg.expectedBaseHeight = (float) datas[0].groundHeight(15, 15); for (IEarthPopulator populator : this.populators) { - populator.populate(this.world, random, cube.getCoords(), biome, datas); + populator.populate(this.world, random, cube.getCoords(), biome, datas, this.settings); } } @@ -406,7 +416,7 @@ public BlockPos getClosestStructure(String name, BlockPos pos, boolean findUnexp * * @author DaPorkchop_ */ - public static class ChunkDataLoader extends CacheLoader> { + public static class ChunkDataLoader extends CacheLoader> { protected final GeneratorDatasets datasets; protected final IEarthDataBaker[] bakers; @@ -416,7 +426,7 @@ public ChunkDataLoader(@NonNull EarthGeneratorSettings settings) { } @Override - public CompletableFuture load(@NonNull ChunkPos pos) { + public CompletableFuture load(@NonNull TilePos pos) { return IEarthAsyncPipelineStep.getFuture(pos, this.datasets, this.bakers, CachedChunkData::builder); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGeneratorPipelines.java b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGeneratorPipelines.java index a8fa38ec..cd1e0d78 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGeneratorPipelines.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGeneratorPipelines.java @@ -3,36 +3,32 @@ import lombok.NonNull; import lombok.experimental.UtilityClass; import net.buildtheearth.terraplusplus.TerraConfig; +import net.buildtheearth.terraplusplus.dataset.IElementDataset; +import net.buildtheearth.terraplusplus.dataset.IScalarDataset; import net.buildtheearth.terraplusplus.dataset.builtin.Climate; import net.buildtheearth.terraplusplus.dataset.builtin.Soil; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; import net.buildtheearth.terraplusplus.dataset.geojson.dataset.ParsingGeoJsonDataset; import net.buildtheearth.terraplusplus.dataset.geojson.dataset.ReferenceResolvingGeoJsonDataset; import net.buildtheearth.terraplusplus.dataset.geojson.dataset.TiledGeoJsonDataset; import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; -import net.buildtheearth.terraplusplus.dataset.scalar.MultiScalarDataset; +import net.buildtheearth.terraplusplus.dataset.scalar.ScalarDatasetConfigurationParser; import net.buildtheearth.terraplusplus.dataset.vector.GeoJsonToVectorDataset; import net.buildtheearth.terraplusplus.dataset.vector.VectorTiledDataset; import net.buildtheearth.terraplusplus.event.InitDatasetsEvent; import net.buildtheearth.terraplusplus.event.InitEarthRegistryEvent; import net.buildtheearth.terraplusplus.generator.biome.IEarthBiomeFilter; -import net.buildtheearth.terraplusplus.generator.biome.Terra121BiomeFilter; -import net.buildtheearth.terraplusplus.generator.biome.UserOverrideBiomeFilter; -import net.buildtheearth.terraplusplus.generator.data.HeightsBaker; import net.buildtheearth.terraplusplus.generator.data.IEarthDataBaker; -import net.buildtheearth.terraplusplus.generator.data.InitialBiomesBaker; -import net.buildtheearth.terraplusplus.generator.data.NullIslandBaker; -import net.buildtheearth.terraplusplus.generator.data.OSMBaker; -import net.buildtheearth.terraplusplus.generator.data.TreeCoverBaker; -import net.buildtheearth.terraplusplus.generator.populate.BiomeDecorationPopulator; import net.buildtheearth.terraplusplus.generator.populate.CompatibilityEarthPopulators; import net.buildtheearth.terraplusplus.generator.populate.IEarthPopulator; -import net.buildtheearth.terraplusplus.generator.populate.SnowPopulator; -import net.buildtheearth.terraplusplus.generator.populate.TreePopulator; import net.buildtheearth.terraplusplus.util.OrderedRegistry; +import net.buildtheearth.terraplusplus.util.bvh.BVH; import net.minecraftforge.common.MinecraftForge; import java.lang.reflect.Array; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; import static net.daporkchop.lib.common.util.PorkUtil.*; @@ -61,46 +57,53 @@ private T[] fire(@NonNull InitEarthRegistryEvent event) { public Map datasets(@NonNull EarthGeneratorSettings settings) { InitDatasetsEvent event = new InitDatasetsEvent(settings); - event.register(KEY_DATASET_HEIGHTS, new MultiScalarDataset(KEY_DATASET_HEIGHTS, settings.useDefaultHeights())); + //start loading both datasets at once to reduce blocking time + CompletableFuture elevationFuture = ScalarDatasetConfigurationParser.loadAndMergeDatasetsFromManifests(settings.useDefaultHeights() + ? Stream.concat(Stream.of(TerraConfig.elevation.servers), Stream.of(settings.customHeights())) + : Stream.of(settings.customHeights())); + CompletableFuture treeCoverFuture = ScalarDatasetConfigurationParser.loadAndMergeDatasetsFromManifests(settings.useDefaultTreeCover() + ? Stream.concat(Stream.of(TerraConfig.treeCover.servers), Stream.of(settings.customTreeCover())) + : Stream.of(settings.customTreeCover())); + event.register(KEY_DATASET_HEIGHTS, elevationFuture.join()); + event.register(KEY_DATASET_TREE_COVER, treeCoverFuture.join()); - ParsingGeoJsonDataset rawOsm = new ParsingGeoJsonDataset(TerraConfig.openstreetmap.servers); + ParsingGeoJsonDataset rawOsm = new ParsingGeoJsonDataset(TerraConfig.openstreetmap.servers_v2); event.register(KEY_DATASET_OSM_RAW, new TiledGeoJsonDataset(new ReferenceResolvingGeoJsonDataset(rawOsm))); - event.register(KEY_DATASET_OSM_PARSED, new VectorTiledDataset(new GeoJsonToVectorDataset(rawOsm, OSMMapper.load(), settings.projection()))); + OSMMapper osmMapper = settings.osmSettings().mapper(); + event.register(KEY_DATASET_OSM_PARSED, osmMapper != null + ? new VectorTiledDataset(new GeoJsonToVectorDataset(rawOsm, osmMapper, settings.projection())) + : IElementDataset.empty(BVH.class)); event.register(KEY_DATASET_TERRA121_PRECIPITATION, new Climate.Precipitation()); event.register(KEY_DATASET_TERRA121_SOIL, new Soil()); event.register(KEY_DATASET_TERRA121_TEMPERATURE, new Climate.Temperature()); - event.register(KEY_DATASET_TREE_COVER, new MultiScalarDataset(KEY_DATASET_TREE_COVER, settings.useDefaultTreeCover())); MinecraftForge.TERRAIN_GEN_BUS.post(event); return event.getAllCustomProperties(); } public IEarthBiomeFilter[] biomeFilters(@NonNull EarthGeneratorSettings settings) { - return fire(new InitEarthRegistryEvent(settings, - uncheckedCast(new OrderedRegistry>() - .addLast("legacy_terra121", new Terra121BiomeFilter()) - .addLast("biome_overrides", new UserOverrideBiomeFilter(settings.projection())))) {}); + OrderedRegistry> registry = new OrderedRegistry<>(); + settings.biomeFilters().forEach(b -> registry.addLast(b.typeId(), b)); + + return fire(new InitEarthRegistryEvent(settings, uncheckedCast(registry)) {}); } public IEarthDataBaker[] dataBakers(@NonNull EarthGeneratorSettings settings) { - return fire(new InitEarthRegistryEvent(settings, - uncheckedCast(new OrderedRegistry>() - .addLast("initial_biomes", new InitialBiomesBaker(settings.biomeProvider())) - .addLast("tree_cover", new TreeCoverBaker()) - .addLast("heights", new HeightsBaker()) - .addLast("osm", new OSMBaker()) - .addLast("null_island", new NullIslandBaker()))) {}); + OrderedRegistry> registry = new OrderedRegistry<>(); + settings.dataBakers().forEach(b -> registry.addLast(b.typeId(), b)); + + return fire(new InitEarthRegistryEvent(settings, uncheckedCast(registry)) {}); } public IEarthPopulator[] populators(@NonNull EarthGeneratorSettings settings) { - return fire(new InitEarthRegistryEvent(settings, - new OrderedRegistry() - .addLast("fml_pre_cube_populate_event", CompatibilityEarthPopulators.cubePopulatePre()) - .addLast("trees", new TreePopulator()) - .addLast("biome_decorate", new BiomeDecorationPopulator(settings)) - .addLast("snow", new SnowPopulator()) - .addLast("fml_post_cube_populate_event", CompatibilityEarthPopulators.cubePopulatePost()) - .addLast("cc_cube_generators_registry", CompatibilityEarthPopulators.cubeGeneratorsRegistry())) {}); + OrderedRegistry registry = new OrderedRegistry<>(); + settings.populators().forEach(b -> registry.addLast(b.typeId(), b)); + + registry.addLast("fml_pre_cube_populate_event", CompatibilityEarthPopulators.cubePopulatePre()) + .addLast("fml_post_cube_populate_event", CompatibilityEarthPopulators.cubePopulatePost()) + .addLast("cc_cube_generators_registry", CompatibilityEarthPopulators.cubeGeneratorsRegistry()); + + return fire(new InitEarthRegistryEvent(settings, uncheckedCast(registry)) {}); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGeneratorSettings.java b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGeneratorSettings.java index 4bde7126..8279af2b 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGeneratorSettings.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGeneratorSettings.java @@ -8,8 +8,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Strings; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -28,9 +26,24 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.With; -import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.TerraMod; import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; +import net.buildtheearth.terraplusplus.generator.biome.BiomeFilterTerra121; +import net.buildtheearth.terraplusplus.generator.biome.BiomeFilterUserOverride; +import net.buildtheearth.terraplusplus.generator.biome.IEarthBiomeFilter; +import net.buildtheearth.terraplusplus.generator.data.DataBakerHeights; +import net.buildtheearth.terraplusplus.generator.data.DataBakerInitialBiomes; +import net.buildtheearth.terraplusplus.generator.data.DataBakerNullIsland; +import net.buildtheearth.terraplusplus.generator.data.DataBakerOSM; +import net.buildtheearth.terraplusplus.generator.data.DataBakerTreeCover; +import net.buildtheearth.terraplusplus.generator.data.IEarthDataBaker; +import net.buildtheearth.terraplusplus.generator.populate.PopulatorBiomeDecoration; +import net.buildtheearth.terraplusplus.generator.populate.IEarthPopulator; +import net.buildtheearth.terraplusplus.generator.populate.PopulatorSnow; +import net.buildtheearth.terraplusplus.generator.populate.PopulatorTrees; +import net.buildtheearth.terraplusplus.generator.settings.GeneratorTerrainSettings; +import net.buildtheearth.terraplusplus.generator.settings.osm.GeneratorOSMSettings; +import net.buildtheearth.terraplusplus.generator.settings.osm.GeneratorOSMSettingsDefault; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.transform.FlipHorizontalProjectionTransform; import net.buildtheearth.terraplusplus.projection.transform.FlipVerticalProjectionTransform; @@ -38,23 +51,33 @@ import net.buildtheearth.terraplusplus.projection.transform.ProjectionTransform; import net.buildtheearth.terraplusplus.projection.transform.ScaleProjectionTransform; import net.buildtheearth.terraplusplus.projection.transform.SwapAxesProjectionTransform; +import net.buildtheearth.terraplusplus.util.TerraConstants; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; import net.daporkchop.lib.binary.oio.StreamUtil; -import net.daporkchop.lib.common.ref.Ref; -import net.minecraft.world.biome.BiomeProvider; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; +import net.minecraft.world.WorldServer; import net.minecraftforge.event.terraingen.DecorateBiomeEvent; import net.minecraftforge.event.terraingen.PopulateChunkEvent; +import org.opengis.referencing.crs.CoordinateReferenceSystem; import java.io.IOException; import java.io.InputStream; -import java.util.Collections; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; import java.util.List; import java.util.Set; +import static java.nio.file.StandardCopyOption.*; +import static java.nio.file.StandardOpenOption.*; +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; import static net.daporkchop.lib.common.util.PValidation.*; -@JsonInclude(JsonInclude.Include.NON_EMPTY) @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @With +@JsonInclude(JsonInclude.Include.NON_ABSENT) public class EarthGeneratorSettings { public static final int CONFIG_VERSION = 2; public static final String DEFAULT_SETTINGS; @@ -66,16 +89,24 @@ public class EarthGeneratorSettings { static { try { try (InputStream in = EarthGeneratorSettings.class.getResourceAsStream("default_generator_settings.json5")) { - DEFAULT_SETTINGS = new String(StreamUtil.toByteArray(in)); + DEFAULT_SETTINGS = new String(StreamUtil.toByteArray(in), StandardCharsets.UTF_8); } try (InputStream in = EarthGeneratorSettings.class.getResourceAsStream("bte_generator_settings.json5")) { - BTE_DEFAULT_SETTINGS = new String(StreamUtil.toByteArray(in)); + BTE_DEFAULT_SETTINGS = new String(StreamUtil.toByteArray(in), StandardCharsets.UTF_8); } } catch (IOException e) { throw new RuntimeException(e); } } + public static EarthGeneratorSettings forWorld(@NonNull WorldServer world) { + Path settingFile = settingsFile(world.getSaveHandler().getWorldDirectory().toPath()); + if (!Files.exists(settingFile)) { + writeSettings(settingFile, world.getWorldInfo().getGeneratorOptions()); + } + return parse(readSettings(settingFile)); + } + /** * Parses the given generator settings. * @@ -113,15 +144,37 @@ public static EarthGeneratorSettings parseUncached(String generatorSettings) { projection = new ScaleProjectionTransform(projection, legacy.scaleX, legacy.scaleY); } - return new EarthGeneratorSettings(projection, legacy.customcubic, true, true, Collections.emptyList(), Collections.emptyList(), CONFIG_VERSION); + return new EarthGeneratorSettings(projection, legacy.customcubic, true, null, true, null, null, null, null, null, null, null, null, CONFIG_VERSION); } return TerraConstants.JSON_MAPPER.readValue(generatorSettings, EarthGeneratorSettings.class); } + public static Path settingsFile(@NonNull Path worldDirectory) { + return worldDirectory.resolve("data").resolve(MODID).resolve("generator_settings.json5"); + } + + @SneakyThrows(IOException.class) + public static String readSettings(@NonNull Path settingsFile) { + return new String(Files.readAllBytes(settingsFile), StandardCharsets.UTF_8); + } + + @SneakyThrows(IOException.class) + public static void writeSettings(@NonNull Path settingsFile, @NonNull String settings) { + Files.createDirectories(settingsFile.getParent()); + Path tmpFile = settingsFile.resolveSibling(settingsFile.getFileName().toString() + ".tmp"); + settings = JSON_MAPPER.readTree(settings).toPrettyString(); + Files.write(tmpFile, settings.getBytes(StandardCharsets.UTF_8), CREATE, TRUNCATE_EXISTING, WRITE, SYNC); + Files.move(tmpFile, settingsFile, REPLACE_EXISTING, ATOMIC_MOVE); + } + @NonNull @Getter(onMethod_ = { @JsonGetter }) protected final GeographicProjection projection; + + @Getter(lazy = true) + private final CoordinateReferenceSystem crs = SISHelper.projectedCRS(this.projection); + @NonNull @Getter(onMethod_ = { @JsonGetter }) protected final String cwg; @@ -129,15 +182,32 @@ public static EarthGeneratorSettings parseUncached(String generatorSettings) { @Getter(onMethod_ = { @JsonGetter }) protected final boolean useDefaultHeights; @Getter(onMethod_ = { @JsonGetter }) + protected final String[][] customHeights; + @Getter(onMethod_ = { @JsonGetter }) protected final boolean useDefaultTreeCover; + @Getter(onMethod_ = { @JsonGetter }) + protected final String[][] customTreeCover; + + @Getter(onMethod_ = { @JsonGetter }) + protected final List biomeFilters; + @Getter(onMethod_ = { @JsonGetter }) + protected final List dataBakers; + @Getter(onMethod_ = { @JsonGetter }) + protected final List populators; + + @Getter(onMethod_ = { @JsonGetter }) + protected final GeneratorOSMSettings osmSettings; + @Getter(onMethod_ = { @JsonGetter }) + protected final GeneratorTerrainSettings terrainSettings; - protected transient final Ref biomeProvider = Ref.soft(() -> new EarthBiomeProvider(this)); - protected transient final Ref customCubic = Ref.soft(() -> { + protected transient final Cached biomeProvider = Cached.global(() -> new EarthBiomeProvider(this), ReferenceStrength.SOFT); + protected transient final Cached customCubic = Cached.global(() -> { CustomGeneratorSettings cfg; if (this.cwg().isEmpty()) { //use new minimal defaults cfg = new CustomGeneratorSettings(); cfg.mineshafts = cfg.caves = cfg.strongholds = cfg.dungeons = cfg.ravines = false; cfg.lakes.clear(); + cfg.waterLevel = 0; } else { try { cfg = CustomGenSettingsSerialization.jankson().fromJsonCarefully(this.cwg(), CustomGeneratorSettings.class); @@ -148,22 +218,28 @@ public static EarthGeneratorSettings parseUncached(String generatorSettings) { throw new RuntimeException(message, err); } } - cfg.waterLevel = 0; return cfg; - }); - protected transient final Ref datasets = Ref.soft(() -> new GeneratorDatasets(this)); + }, ReferenceStrength.SOFT); + protected transient final Cached datasets = Cached.global(() -> new GeneratorDatasets(this), ReferenceStrength.SOFT); - @Getter - protected transient final Set skipChunkPopulation; - @Getter - protected transient final Set skipBiomeDecoration; + @Getter(onMethod_ = { @JsonGetter }) + protected final Set skipChunkPopulation; + @Getter(onMethod_ = { @JsonGetter }) + protected final Set skipBiomeDecoration; @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) public EarthGeneratorSettings( @JsonProperty(value = "projection", required = true) @NonNull GeographicProjection projection, @JsonProperty(value = "cwg") String cwg, @JsonProperty(value = "useDefaultHeights") Boolean useDefaultHeights, + @JsonProperty(value = "customHeights") String[][] customHeights, @JsonProperty(value = "useDefaultTreeCover") @JsonAlias("useDefaultTrees") Boolean useDefaultTreeCover, + @JsonProperty(value = "customTreeCover") String[][] customTreeCover, + @JsonProperty(value = "biomeFilters") List biomeFilters, + @JsonProperty(value = "dataBakers") List dataBakers, + @JsonProperty(value = "populators") List populators, + @JsonProperty(value = "osmSettings") GeneratorOSMSettings osmSettings, + @JsonProperty(value = "terrainSettings") GeneratorTerrainSettings terrainSettings, @JsonProperty(value = "skipChunkPopulation") List skipChunkPopulation, @JsonProperty(value = "skipBiomeDecoration") List skipBiomeDecoration, @JsonProperty(value = "version", required = true) int version) { @@ -172,19 +248,40 @@ public EarthGeneratorSettings( this.projection = projection; this.cwg = Strings.isNullOrEmpty(cwg) ? "" : CustomGeneratorSettingsFixer.INSTANCE.fixJson(cwg).toJson(JsonGrammar.COMPACT); this.useDefaultHeights = useDefaultHeights != null ? useDefaultHeights : true; + this.customHeights = customHeights != null ? customHeights : new String[0][]; this.useDefaultTreeCover = useDefaultTreeCover != null ? useDefaultTreeCover : true; + this.customTreeCover = customTreeCover != null ? customTreeCover : new String[0][]; + + this.biomeFilters = biomeFilters != null ? biomeFilters : Arrays.asList( + new BiomeFilterTerra121(), + new BiomeFilterUserOverride()); + this.dataBakers = dataBakers != null ? dataBakers : Arrays.asList( + new DataBakerInitialBiomes(), + new DataBakerTreeCover(), + new DataBakerHeights(), + new DataBakerOSM(null), + new DataBakerNullIsland()); + this.populators = populators != null ? populators : Arrays.asList( + new PopulatorTrees(), + new PopulatorBiomeDecoration(), + new PopulatorSnow()); + + this.osmSettings = osmSettings != null ? osmSettings : new GeneratorOSMSettingsDefault(); + this.terrainSettings = terrainSettings != null ? terrainSettings : new GeneratorTerrainSettings(); this.skipChunkPopulation = skipChunkPopulation != null ? Sets.immutableEnumSet(skipChunkPopulation) : Sets.immutableEnumSet(PopulateChunkEvent.Populate.EventType.ICE); this.skipBiomeDecoration = skipBiomeDecoration != null ? Sets.immutableEnumSet(skipBiomeDecoration) : Sets.immutableEnumSet(DecorateBiomeEvent.Decorate.EventType.TREE); } @Override + @SneakyThrows(JsonProcessingException.class) public String toString() { - try { - return TerraConstants.JSON_MAPPER.writeValueAsString(this); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } + return TerraConstants.JSON_MAPPER.writeValueAsString(this); + } + + @SneakyThrows(JsonProcessingException.class) + public String toPrettyString() { + return TerraConstants.JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(this); } @JsonGetter("version") @@ -192,16 +289,6 @@ private int version() { return CONFIG_VERSION; } - @JsonGetter("skipChunkPopulation") - private PopulateChunkEvent.Populate.EventType[] getSkipChunkPopulation() { - return this.skipChunkPopulation.toArray(new PopulateChunkEvent.Populate.EventType[0]); - } - - @JsonGetter("skipBiomeDecoration") - private DecorateBiomeEvent.Decorate.EventType[] getSkipBiomeDecoration() { - return this.skipBiomeDecoration.toArray(new DecorateBiomeEvent.Decorate.EventType[0]); - } - public EarthBiomeProvider biomeProvider() { return this.biomeProvider.get(); } @@ -221,6 +308,7 @@ public GeneratorDatasets datasets() { * @author SmylerMC */ @JsonIgnore + @Deprecated public String getLegacyGeneratorString() { LegacyConfig legacy = new LegacyConfig(); legacy.scaleX = 1; @@ -279,8 +367,6 @@ public String getLegacyGeneratorString() { } } - @JsonDeserialize - @JsonSerialize private static class LegacyConfig { private static GeographicProjection orientProjectionLegacy(GeographicProjection base, Orientation orientation) { if (base.upright()) { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/GeneratorDatasets.java b/src/main/java/net/buildtheearth/terraplusplus/generator/GeneratorDatasets.java index f639b850..7cf1d8bc 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/GeneratorDatasets.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/GeneratorDatasets.java @@ -12,11 +12,13 @@ */ @Getter public class GeneratorDatasets extends CustomAttributeContainer { + protected final EarthGeneratorSettings settings; protected final GeographicProjection projection; public GeneratorDatasets(@NonNull EarthGeneratorSettings settings) { super(EarthGeneratorPipelines.datasets(settings)); + this.settings = settings; this.projection = settings.projection(); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java b/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java index 2811ff64..6ebe25b3 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java @@ -1,35 +1,45 @@ package net.buildtheearth.terraplusplus.generator; -import io.github.opencubicchunks.cubicchunks.api.util.Coords; import net.buildtheearth.terraplusplus.TerraMod; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; -import net.minecraft.util.math.ChunkPos; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.buildtheearth.terraplusplus.util.geo.pointarray.AxisAlignedGridPointArray2D; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; import java.util.Arrays; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; import static net.daporkchop.lib.common.util.PorkUtil.*; /** * @author DaPorkchop_ */ public interface IEarthAsyncPipelineStep> { - static > CompletableFuture getFuture(ChunkPos pos, GeneratorDatasets datasets, IEarthAsyncPipelineStep[] steps, Supplier builderFactory) { - int baseX = Coords.cubeToMinBlock(pos.x); - int baseZ = Coords.cubeToMinBlock(pos.z); + static > CompletableFuture getFuture(TilePos pos, GeneratorDatasets datasets, IEarthAsyncPipelineStep[] steps, Supplier builderFactory) { + int baseX = pos.blockX(); + int baseZ = pos.blockZ(); + int sizeBlocks = pos.sizeBlocks(); CompletableFuture[] futures = new CompletableFuture[steps.length]; try { - Bounds2d chunkBounds = Bounds2d.of(baseX, baseX + 16, baseZ, baseZ + 16); + Bounds2d chunkBounds = Bounds2d.of(baseX, baseX + sizeBlocks, baseZ, baseZ + sizeBlocks); CornerBoundingBox2d chunkBoundsGeo = chunkBounds.toCornerBB(datasets.projection(), false).toGeo(); + PointArray2D sampledPoints = new AxisAlignedGridPointArray2D(null, SISHelper.projectedCRS(datasets.projection()), 16, 16, + baseX, baseZ, sizeBlocks, sizeBlocks); + + //TODO: don't do this + sampledPoints = (PointArray2D) sampledPoints.convert(TPP_GEO_CRS, 0.1d); + for (int i = 0; i < steps.length; i++) { try { - futures[i] = steps[i].requestData(pos, datasets, chunkBounds, chunkBoundsGeo); + futures[i] = steps[i].requestData(pos, datasets, chunkBounds, chunkBoundsGeo, sampledPoints); } catch (OutOfProjectionBoundsException ignored) { } } @@ -61,22 +71,23 @@ static > CompletableFuture getFuture(C } /** - * Asynchronously fetches the data required to bake the data for the given column. + * Asynchronously fetches the data required to bake the data for the given tile. * - * @param pos the position of the column - * @param datasets the datasets to be used - * @param bounds the bounding box of the chunk (in blocks) - * @param boundsGeo the bounding box of the chunk (in world coordinates) + * @param pos the position of the tile + * @param datasets the datasets to be used + * @param bounds the bounding box of the chunk (in blocks) + * @param boundsGeo the bounding box of the chunk (in world coordinates) + * @param sampledPoints * @return a {@link CompletableFuture} which will be completed with the required data */ - CompletableFuture requestData(ChunkPos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException; + CompletableFuture requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo, PointArray2D sampledPoints) throws OutOfProjectionBoundsException; /** - * Bakes the retrieved data into the chunk data for the given column. + * Bakes the retrieved data into the chunk data for the given tile. * - * @param pos the position of the column + * @param pos the position of the tile * @param builder the builder for the cached chunk data * @param data the data to bake */ - void bake(ChunkPos pos, B builder, D data); + void bake(TilePos pos, B builder, D data); } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java b/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java index 3d436374..37d03c2e 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java @@ -4,17 +4,17 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import lombok.NonNull; -import net.buildtheearth.terraplusplus.generator.data.TreeCoverBaker; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.generator.data.DataBakerTreeCover; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.EmptyWorld; import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.http.Http; +import net.daporkchop.lib.binary.oio.StreamUtil; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Bootstrap; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; -import net.minecraftforge.client.model.pipeline.LightUtil; import javax.swing.JFrame; import java.awt.Canvas; @@ -27,6 +27,7 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.CompletableFuture; @@ -58,7 +59,8 @@ public static void main(String... args) throws OutOfProjectionBoundsException { } } - private static void doThing() throws OutOfProjectionBoundsException { //allows hot-swapping + @SneakyThrows({ IOException.class, OutOfProjectionBoundsException.class }) + private static void doThing() { //allows hot-swapping final int COUNT = 5; final int SHIFT = 1; final int CANVAS_SIZE = SIZE * COUNT; @@ -200,13 +202,36 @@ public void dispose() { } } - State state = new State(EarthGeneratorSettings.parseUncached(EarthGeneratorSettings.BTE_DEFAULT_SETTINGS)); + State state = new State(EarthGeneratorSettings.parseUncached(new String(StreamUtil.toByteArray(EarthGeneratorSettings.class.getResourceAsStream("bte_generator_settings.json5"))))); state.initSettings(); - double[] proj = state.projection.fromGeo(8.57696d, 47.21763d); //switzerland - proj = state.projection.fromGeo(12.58589, 55.68841); //copenhagen - //proj = state.projection.fromGeo(24.7535, 59.4435); //tallinn - proj = state.projection.fromGeo(14.50513, 46.05108); //ljubljana + double[] proj = new double[2]; //null island + proj = state.projection.fromGeo(8.57696d, 47.21763d); //steinhausen, switzerland + //proj = state.projection.fromGeo(12.58589, 55.68841); //copenhagen, denmark + //proj = state.projection.fromGeo(24.7535, 59.4435); //tallinn, estonia + //proj = state.projection.fromGeo(14.50513, 46.05108); //ljubljana, slovenia + //proj = state.projection.fromGeo(2.29118, 48.86020); //paris, france + //proj = state.projection.fromGeo(-9.42956, 52.97183); //cliffs of moher, ireland + //proj = state.projection.fromGeo(9.70089, 39.92472); //tortoli, italy + //proj = state.projection.fromGeo(15.085464455006724, 37.50954065726297); //somewhere in sicily + //proj = state.projection.fromGeo(12.610463237424899, 37.673937184583636); //somewhere in sicily + //proj = state.projection.fromGeo(9.6726, 45.6699); //lombardia, italy + //proj = state.projection.fromGeo(8.93058, 44.40804); //genova, italy + //proj = state.projection.fromGeo(16.5922, 38.9069); //catanzaro, italy + //proj = state.projection.fromGeo(-3.7070, 40.4168); //madrid, spain + //proj = state.projection.fromGeo(-5.57589, 37.47938); //middle of nowhere, spain + //proj = state.projection.fromGeo(13.37156, 52.52360); //berlin, germany + //proj = state.projection.fromGeo(11.63779, 52.11903); //magdeburg, germany + //proj = state.projection.fromGeo(7.206603551122279, 50.66019804133367); //röhndorf, germany + //proj = state.projection.fromGeo(12.35027, 51.33524); //leipzig, germany + //proj = state.projection.fromGeo(14.80963, 50.88887); //zittau, germany + //proj = state.projection.fromGeo(9.60552, 50.79986); //niederaula, germany + //proj = state.projection.fromGeo(-6.25900, 53.34702); //dublin, ireland + //proj = state.projection.fromGeo(5.33831, 50.22487); //marche-en-famenne, belgium + //proj = state.projection.fromGeo(6.14179, 49.61317); //luxembourg, luxembourg + //proj = state.projection.fromGeo(-123.02556, 49.30506); //vancouver, canada + //proj = state.projection.fromGeo(4.34115, 50.85378); //brussels, belgium + //proj = state.projection.fromGeo(4.90607, 52.38375); //amsterdam, netherlands state.setView(floorI(proj[0]) >> 4, floorI(proj[1]) >> 4, 0); state.update(); @@ -252,11 +277,65 @@ public CompletableFuture load(@NonNull TilePos pos) { } } + protected void writeTileToImg(BufferedImage dst, CachedChunkData data, int baseX, int baseZ) { + byte[] treeCoverArr = data.getCustom(EarthGeneratorPipelines.KEY_DATA_TREE_COVER, DataBakerTreeCover.FALLBACK_TREE_DENSITY); + + for (int cx = 0; cx < 16; cx++) { + for (int cz = 0; cz < 16; cz++) { + int c; + + IBlockState state = data.surfaceBlock(cx, cz); + if (state != null) { + c = state.getMapColor(EmptyWorld.INSTANCE, BlockPos.ORIGIN).colorValue; + } else { + int groundHeight = data.groundHeight(cx, cz); + int waterHeight = data.waterHeight(cx, cz); + + int r = 0; + int g = 0; + int b = 0; + + { + float dx = cx == 15 ? groundHeight - data.groundHeight(cx - 1, cz) : data.groundHeight(cx + 1, cz) - groundHeight; + float dz = cz == 15 ? groundHeight - data.groundHeight(cx, cz - 1) : data.groundHeight(cx, cz + 1) - groundHeight; + if (dx != 0.0f) { + if (dx < 0.0f) { + r = 0xFF; + } else { + g = b = 0xFF; + } + } + if (dz != 0.0f) { + if (dz < 0.0f) { + r = 0xFF; + } else { + g = b = 0xFF; + } + } + } + + if (groundHeight < waterHeight) { + b = lerpI(255, 64, clamp(waterHeight - groundHeight + 1, 0, 8) / 8.0f); + } + + g = max(g, lerpI(0, 80, (treeCoverArr[cx * 16 + cz] & 0xFF) * DataBakerTreeCover.TREE_AREA * (1.0d / 255.0d))); + c = r << 16 | g << 8 | b; + } + + dst.setRGB(baseX + cx, baseZ + cz, 0xFF000000 | c); + } + } + } + protected CompletableFuture baseZoomTile(int x, int z) { + return this.zoomedOutTile(x, z, 0); + } + + protected CompletableFuture zoomedOutTile(int x, int z, int zoom) { CompletableFuture[] dataFutures = uncheckedCast(new CompletableFuture[CHUNKS_PER_TILE * CHUNKS_PER_TILE]); for (int i = 0, dx = 0; dx < CHUNKS_PER_TILE; dx++) { for (int dz = 0; dz < CHUNKS_PER_TILE; dz++) { - dataFutures[i++] = this.loader.load(new ChunkPos((x << CHUNKS_PER_TILE_SHIFT) + dx, (z << CHUNKS_PER_TILE_SHIFT) + dz)); + dataFutures[i++] = this.loader.load(new TilePos((x << CHUNKS_PER_TILE_SHIFT) + dx, (z << CHUNKS_PER_TILE_SHIFT) + dz, zoom)); } } @@ -265,53 +344,14 @@ protected CompletableFuture baseZoomTile(int x, int z) { for (int ti = 0, tx = 0; tx < CHUNKS_PER_TILE; tx++) { for (int tz = 0; tz < CHUNKS_PER_TILE; tz++) { - CachedChunkData data = dataFutures[ti++].join(); - - byte[] treeCoverArr = data.getCustom(EarthGeneratorPipelines.KEY_DATA_TREE_COVER, TreeCoverBaker.FALLBACK_TREE_DENSITY); - - int baseX = tx << 4; - int baseZ = tz << 4; - for (int cx = 0; cx < 16; cx++) { - for (int cz = 0; cz < 16; cz++) { - int c; - - IBlockState state = data.surfaceBlock(cx, cz); - if (state != null) { - c = state.getMapColor(EmptyWorld.INSTANCE, BlockPos.ORIGIN).colorValue; - } else { - int groundHeight = data.groundHeight(cx, cz); - int waterHeight = data.waterHeight(cx, cz); - - int r; - int g; - int b; - - if (groundHeight > waterHeight) { - float dx = cx == 15 ? groundHeight - data.groundHeight(cx - 1, cz) : data.groundHeight(cx + 1, cz) - groundHeight; - float dz = cz == 15 ? groundHeight - data.groundHeight(cx, cz - 1) : data.groundHeight(cx, cz + 1) - groundHeight; - int diffuse = floorI(LightUtil.diffuseLight(clamp(dx, -1.0f, 1.0f), 0.0f, clamp(dz, -1.0f, 1.0f)) * 255.0f); - r = g = b = diffuse; - } else { - r = g = 0; - b = lerpI(255, 64, clamp(waterHeight - groundHeight + 1, 0, 8) / 8.0f); - } - - g = max(g, lerpI(0, 80, (treeCoverArr[cx * 16 + cz] & 0xFF) * TreeCoverBaker.TREE_AREA * (1.0d / 255.0d))); - c = r << 16 | g << 8 | b; - } - - dst.setRGB(baseX + cx, baseZ + cz, 0xFF000000 | c); - } - } + this.writeTileToImg(dst, dataFutures[ti++].join(), tx << 4, tz << 4); } } return dst; }); - } - protected CompletableFuture zoomedOutTile(int x, int z, int zoom) { - CompletableFuture[] children = uncheckedCast(new CompletableFuture[4]); + /*CompletableFuture[] children = uncheckedCast(new CompletableFuture[4]); for (int i = 0, dx = 0; dx < 2; dx++) { for (int dz = 0; dz < 2; dz++) { children[i++] = this.tile((x << 1) | dx, (z << 1) | dz, zoom - 1); @@ -349,7 +389,7 @@ protected CompletableFuture zoomedOutTile(int x, int z, int zoom) } return dst; - }); + });*/ } protected CompletableFuture zoomedInTile(int x, int z, int zoom) { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterConstant.java b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterConstant.java new file mode 100644 index 00000000..b85a4541 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterConstant.java @@ -0,0 +1,46 @@ +package net.buildtheearth.terraplusplus.generator.biome; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.ChunkBiomesBuilder; +import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; +import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; +import net.minecraft.world.biome.Biome; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +/** + * Generates a single, fixed biome in the entire world. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class BiomeFilterConstant implements IEarthBiomeFilter { + protected final Biome biome; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public BiomeFilterConstant( + @JsonProperty(value = "biome", required = true) @NonNull Biome biome) { + this.biome = biome; + } + + @Override + public CompletableFuture requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo, PointArray2D sampledPoints) throws OutOfProjectionBoundsException { + return null; + } + + @Override + public void bake(TilePos pos, ChunkBiomesBuilder builder, Void data) { + Arrays.fill(builder.state(), this.biome); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/biome/Terra121BiomeFilter.java b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterTerra121.java similarity index 90% rename from src/main/java/net/buildtheearth/terraplusplus/generator/biome/Terra121BiomeFilter.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterTerra121.java index 2c88f129..5a0afe21 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/biome/Terra121BiomeFilter.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterTerra121.java @@ -1,5 +1,6 @@ package net.buildtheearth.terraplusplus.generator.biome; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.NonNull; import lombok.RequiredArgsConstructor; import net.buildtheearth.terraplusplus.dataset.IScalarDataset; @@ -9,9 +10,10 @@ import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; import net.minecraft.init.Biomes; -import net.minecraft.util.math.ChunkPos; import net.minecraft.world.biome.Biome; import java.util.Arrays; @@ -22,9 +24,10 @@ * * @author DaPorkchop_ */ -public class Terra121BiomeFilter implements IEarthBiomeFilter { +@JsonDeserialize +public final class BiomeFilterTerra121 implements IEarthBiomeFilter { @Override - public CompletableFuture requestData(ChunkPos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException { + public CompletableFuture requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo, PointArray2D sampledPoints) throws OutOfProjectionBoundsException { CompletableFuture precipitationFuture = datasets.getCustom(EarthGeneratorPipelines.KEY_DATASET_TERRA121_PRECIPITATION).getAsync(boundsGeo, 16, 16); CompletableFuture soilFuture = datasets.getCustom(EarthGeneratorPipelines.KEY_DATASET_TERRA121_SOIL).getAsync(boundsGeo, 16, 16); CompletableFuture temperatureFuture = datasets.getCustom(EarthGeneratorPipelines.KEY_DATASET_TERRA121_TEMPERATURE).getAsync(boundsGeo, 16, 16); @@ -34,7 +37,7 @@ public CompletableFuture requestData(ChunkPos pos, Gen } @Override - public void bake(ChunkPos pos, ChunkBiomesBuilder builder, Terra121BiomeFilter.Data data) { + public void bake(TilePos pos, ChunkBiomesBuilder builder, BiomeFilterTerra121.Data data) { Biome[] biomes = builder.state(); if (data == null) { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterUserOverride.java b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterUserOverride.java new file mode 100644 index 00000000..225d855e --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/BiomeFilterUserOverride.java @@ -0,0 +1,134 @@ +package net.buildtheearth.terraplusplus.generator.biome; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.collect.ImmutableSet; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.generator.ChunkBiomesBuilder; +import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; +import net.buildtheearth.terraplusplus.util.bvh.BVH; +import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; +import net.minecraft.world.biome.Biome; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public class BiomeFilterUserOverride implements IEarthBiomeFilter { + @Getter(onMethod_ = { @JsonGetter }) + protected final BiomeOverrideArea[] areas; + protected final BVH bvh; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public BiomeFilterUserOverride( + @JsonProperty(value = "areas", required = true) @NonNull BiomeOverrideArea... areas) { + this.areas = areas; + this.bvh = BVH.of(areas); + } + + @Override + public CompletableFuture requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo, PointArray2D sampledPoints) throws OutOfProjectionBoundsException { + return CompletableFuture.supplyAsync(() -> this.bvh.getAllIntersecting(boundsGeo).stream() + .max(Comparator.naturalOrder()) + .orElse(null)); + } + + @Override + public void bake(TilePos pos, ChunkBiomesBuilder builder, BiomeOverrideArea bbox) { + if (bbox == null) { //out of bounds, or no override at this position + return; + } + + if (bbox.replace == null) { //all biomes are overridden + Arrays.fill(builder.state(), bbox.biome); + } else { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + if (bbox.replace.contains(builder.get(x, z))) { + builder.set(x, z, bbox.biome); + } + } + } + } + } + + /** + * Sets the biome in a specific bounding box. + * + * @author DaPorkchop_ + */ + @Getter(onMethod_ = { @JsonGetter }) + @JsonDeserialize + public static class BiomeOverrideArea implements Bounds2d, Comparable { + protected final GeographicProjection projection; + protected final Geometry geometry; + + protected final Set replace; + protected final Biome biome; + + protected final double priority; + + @Getter(AccessLevel.NONE) + protected final Bounds2d bounds; + + @JsonCreator + @SneakyThrows(OutOfProjectionBoundsException.class) + public BiomeOverrideArea( + @JsonProperty(value = "projection", required = true) @NonNull GeographicProjection projection, + @JsonProperty(value = "geometry", required = true) @NonNull Geometry geometry, + @JsonProperty(value = "replace", required = false) Set replace, + @JsonProperty(value = "biome", required = true) @JsonAlias({ "with" }) @NonNull Biome biome, + @JsonProperty(value = "priority", required = false) double priority) { + this.projection = projection; + this.geometry = geometry; + this.bounds = geometry.project(projection::toGeo).bounds(); + + this.replace = replace != null ? ImmutableSet.copyOf(replace) : null; + this.biome = biome; + + this.priority = priority; + } + + @Override + public int compareTo(BiomeOverrideArea o) { + return -Double.compare(this.priority, o.priority); + } + + @Override + public double minX() { + return this.bounds.minX(); + } + + @Override + public double maxX() { + return this.bounds.maxX(); + } + + @Override + public double minZ() { + return this.bounds.minZ(); + } + + @Override + public double maxZ() { + return this.bounds.maxZ(); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/biome/IEarthBiomeFilter.java b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/IEarthBiomeFilter.java index 20767a46..8957bffb 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/biome/IEarthBiomeFilter.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/IEarthBiomeFilter.java @@ -1,12 +1,35 @@ package net.buildtheearth.terraplusplus.generator.biome; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; import net.buildtheearth.terraplusplus.generator.ChunkBiomesBuilder; import net.buildtheearth.terraplusplus.generator.IEarthAsyncPipelineStep; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; import net.minecraft.world.biome.Biome; +import static net.daporkchop.lib.common.util.PValidation.*; + /** * @author DaPorkchop_ */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonTypeIdResolver(IEarthBiomeFilter.TypeIdResolver.class) +@JsonDeserialize public interface IEarthBiomeFilter extends IEarthAsyncPipelineStep, ChunkBiomesBuilder> { + /** + * @return this {@link IEarthBiomeFilter}'s type ID + */ + default String typeId() { + String typeId = GlobalParseRegistries.GENERATOR_SETTINGS_BIOME_FILTER.inverse().get(this.getClass()); + checkState(typeId != null, "unknown IEarthBiomeFilter implementation: %s", this.getClass()); + return typeId; + } + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.GENERATOR_SETTINGS_BIOME_FILTER); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/biome/UserOverrideBiomeFilter.java b/src/main/java/net/buildtheearth/terraplusplus/generator/biome/UserOverrideBiomeFilter.java deleted file mode 100644 index 1f2f055e..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/biome/UserOverrideBiomeFilter.java +++ /dev/null @@ -1,135 +0,0 @@ -package net.buildtheearth.terraplusplus.generator.biome; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.google.common.collect.ImmutableSet; -import lombok.Getter; -import lombok.NonNull; -import lombok.SneakyThrows; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.generator.ChunkBiomesBuilder; -import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; -import net.buildtheearth.terraplusplus.projection.GeographicProjection; -import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; -import net.buildtheearth.terraplusplus.util.bvh.BVH; -import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; -import net.buildtheearth.terraplusplus.util.http.Disk; -import net.daporkchop.lib.common.function.io.IOFunction; -import net.daporkchop.lib.common.function.throwing.EFunction; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.world.biome.Biome; - -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; - -/** - * @author DaPorkchop_ - */ -public class UserOverrideBiomeFilter implements IEarthBiomeFilter { - protected final BVH bvh; - - @SneakyThrows(IOException.class) - public UserOverrideBiomeFilter(@NonNull GeographicProjection projection) { - List configSources = new ArrayList<>(); - configSources.add(UserOverrideBiomeFilter.class.getResource("biome_overrides.json5")); - - try (Stream stream = Files.list(Files.createDirectories(Disk.configFile("biome_overrides")))) { - stream.filter(Files::isRegularFile) - .filter(p -> p.getFileName().toString().matches(".*\\.json5?$")) - .map(Path::toUri).map((EFunction) URI::toURL) - .forEach(configSources::add); - } - - this.bvh = BVH.of(configSources.stream() - .map((IOFunction) url -> TerraConstants.JSON_MAPPER.readValue(url, BiomeBoundingBox[].class)) - .flatMap(Arrays::stream) - .toArray(BiomeBoundingBox[]::new)); - } - - @Override - public CompletableFuture requestData(ChunkPos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException { - return CompletableFuture.supplyAsync(() -> this.bvh.getAllIntersecting(boundsGeo).stream() - .max(Comparator.naturalOrder()) - .orElse(null)); - } - - @Override - public void bake(ChunkPos pos, ChunkBiomesBuilder builder, BiomeBoundingBox bbox) { - if (bbox == null) { //out of bounds, or no override at this position - return; - } - - if (bbox.replace == null) { //all biomes are overridden - Arrays.fill(builder.state(), bbox.biome); - } else { - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - if (bbox.replace.contains(builder.get(x, z))) { - builder.set(x, z, bbox.biome); - } - } - } - } - } - - /** - * Sets the biome in a specific bounding box. - * - * @author DaPorkchop_ - */ - @JsonDeserialize - @JsonSerialize - @Getter - public static class BiomeBoundingBox implements Bounds2d, Comparable { - protected final Set replace; - protected final Biome biome; - - protected final double minX; - protected final double maxX; - protected final double minZ; - protected final double maxZ; - - @Getter(onMethod_ = { @JsonGetter }) - protected final double priority; - - @JsonCreator - public BiomeBoundingBox( - @JsonProperty(value = "replace", required = false) Biome[] replace, - @JsonProperty(value = "biome", required = true) @NonNull Biome biome, - @JsonProperty(value = "bounds", required = true) @NonNull Bounds2d bounds, - @JsonProperty(value = "priority", defaultValue = "0.0") double priority) { - this.replace = replace != null ? ImmutableSet.copyOf(replace) : null; - this.biome = biome; - this.priority = priority; - - this.minX = bounds.minX(); - this.maxX = bounds.maxX(); - this.minZ = bounds.minZ(); - this.maxZ = bounds.maxZ(); - } - - @Override - public int compareTo(BiomeBoundingBox o) { - return -Double.compare(this.priority, o.priority); - } - - @JsonGetter("bounds") - public Bounds2d bounds() { - return Bounds2d.of(this.minX, this.maxX, this.minZ, this.maxZ); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/data/HeightsBaker.java b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerHeights.java similarity index 70% rename from src/main/java/net/buildtheearth/terraplusplus/generator/data/HeightsBaker.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerHeights.java index 1cfc6c8a..469768f4 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/data/HeightsBaker.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerHeights.java @@ -1,13 +1,15 @@ package net.buildtheearth.terraplusplus.generator.data; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import net.buildtheearth.terraplusplus.dataset.IScalarDataset; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.generator.EarthGeneratorPipelines; import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; -import net.minecraft.util.math.ChunkPos; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; import java.util.Arrays; import java.util.concurrent.CompletableFuture; @@ -17,14 +19,15 @@ /** * @author DaPorkchop_ */ -public class HeightsBaker implements IEarthDataBaker { +@JsonDeserialize +public final class DataBakerHeights implements IEarthDataBaker { @Override - public CompletableFuture requestData(ChunkPos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException { - return datasets.getCustom(EarthGeneratorPipelines.KEY_DATASET_HEIGHTS).getAsync(boundsGeo, 16, 16); + public CompletableFuture requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo, PointArray2D sampledPoints) throws OutOfProjectionBoundsException { + return datasets.getCustom(EarthGeneratorPipelines.KEY_DATASET_HEIGHTS).getAsync(sampledPoints); } @Override - public void bake(ChunkPos pos, CachedChunkData.Builder builder, double[] heights) { + public void bake(TilePos pos, CachedChunkData.Builder builder, double[] heights) { if (heights == null) { //consider heights array to be filled with NaNs Arrays.fill(builder.waterDepth(), (byte) (CachedChunkData.WATERDEPTH_TYPE_OCEAN | ~CachedChunkData.WATERDEPTH_TYPE_MASK)); return; //we assume the builder's heights are already all set to the blank height value diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/data/InitialBiomesBaker.java b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerInitialBiomes.java similarity index 59% rename from src/main/java/net/buildtheearth/terraplusplus/generator/data/InitialBiomesBaker.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerInitialBiomes.java index 5cdcca30..310eaa75 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/data/InitialBiomesBaker.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerInitialBiomes.java @@ -1,35 +1,30 @@ package net.buildtheearth.terraplusplus.generator.data; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import net.buildtheearth.terraplusplus.generator.CachedChunkData; -import net.buildtheearth.terraplusplus.generator.EarthBiomeProvider; import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; -import net.minecraft.util.math.ChunkPos; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; import net.minecraft.world.biome.Biome; -import net.minecraft.world.biome.BiomeProvider; import java.util.concurrent.CompletableFuture; /** * @author DaPorkchop_ */ -@RequiredArgsConstructor -public class InitialBiomesBaker implements IEarthDataBaker> { - @NonNull - protected final EarthBiomeProvider biomeProvider; - +@JsonDeserialize +public final class DataBakerInitialBiomes implements IEarthDataBaker> { @Override - public CompletableFuture> requestData(ChunkPos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException { - return this.biomeProvider.getBiomesForChunkAsync(pos); + public CompletableFuture> requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo, PointArray2D sampledPoints) throws OutOfProjectionBoundsException { + return datasets.settings().biomeProvider().getBiomesForTileAsync(pos); } @Override - public void bake(ChunkPos pos, CachedChunkData.Builder builder, ImmutableCompactArray biomes) { + public void bake(TilePos pos, CachedChunkData.Builder builder, ImmutableCompactArray biomes) { if (biomes == null) { //can occur if chunk coordinates are outside projection bounds return; } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerNullIsland.java b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerNullIsland.java new file mode 100644 index 00000000..544f1412 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerNullIsland.java @@ -0,0 +1,81 @@ +package net.buildtheearth.terraplusplus.generator.data; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.github.opencubicchunks.cubicchunks.api.util.Coords; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; +import net.buildtheearth.terraplusplus.generator.EarthGeneratorPipelines; +import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; +import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; +import net.minecraft.init.Biomes; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import static java.lang.Math.*; + +/** + * Sets the surface height at null island to 1. + * + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class DataBakerNullIsland implements IEarthDataBaker { + public static final int NULL_ISLAND_RADIUS = 2; //the (square) radius of null island, in chunks + + public static boolean isNullIsland(int chunkZ, int chunkX) { + return max(chunkZ ^ (chunkZ >> 31), chunkX ^ (chunkX >> 31)) <= NULL_ISLAND_RADIUS; + } + + @Override + public CompletableFuture requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo, PointArray2D sampledPoints) throws OutOfProjectionBoundsException { + return null; + } + + @Override + public void bake(TilePos pos, CachedChunkData.Builder builder, Void data) { + if (pos.zoom() == 0) { //optimized implementation for zoom lvl 0 + this.bakeZoom0(pos.x(), pos.z(), builder); + } else { //slower, more general implementation for higher zooms + this.bakeZoomHigher(pos.x(), pos.z(), pos.zoom(), builder); + } + } + + protected void bakeZoom0(int chunkX, int chunkZ, CachedChunkData.Builder builder) { + if (isNullIsland(chunkX, chunkZ)) { + Arrays.fill(builder.surfaceHeight(), -1); + + byte[] trees = builder.getCustom(EarthGeneratorPipelines.KEY_DATA_TREE_COVER, null); + if (trees != null) { + Arrays.fill(trees, (byte) 0); + } + + Arrays.fill(builder.biomes(), ((chunkX ^ (chunkX >> 31)) | (chunkZ ^ (chunkZ >> 31))) == 0 ? Biomes.FOREST : Biomes.PLAINS); + Arrays.fill(builder.waterDepth(), (byte) CachedChunkData.WATERDEPTH_DEFAULT); + } + } + + protected void bakeZoomHigher(int tileX, int tileZ, int zoom, CachedChunkData.Builder builder) { + byte[] trees = builder.getCustom(EarthGeneratorPipelines.KEY_DATA_TREE_COVER, null); + + for (int i = 0, dx = 0; dx < 16; dx++) { + for (int dz = 0; dz < 16; dz++, i++) { + int chunkX = (tileX << zoom) + Coords.blockToCube(dx << zoom); + int chunkZ = (tileZ << zoom) + Coords.blockToCube(dz << zoom); + if (isNullIsland(chunkX, chunkZ)) { + builder.surfaceHeight()[i] = -1; + + if (trees != null) { + trees[i] = (byte) 0; + } + + builder.biomes()[i] = ((chunkX ^ (chunkX >> 31)) | (chunkZ ^ (chunkZ >> 31))) == 0 ? Biomes.FOREST : Biomes.PLAINS; + builder.waterDepth()[i] = (byte) CachedChunkData.WATERDEPTH_DEFAULT; + } + } + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/data/OSMBaker.java b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerOSM.java similarity index 52% rename from src/main/java/net/buildtheearth/terraplusplus/generator/data/OSMBaker.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerOSM.java index d411b947..679a12eb 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/data/OSMBaker.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerOSM.java @@ -1,6 +1,10 @@ package net.buildtheearth.terraplusplus.generator.data; -import io.github.opencubicchunks.cubicchunks.api.util.Coords; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; import net.buildtheearth.terraplusplus.dataset.IElementDataset; import net.buildtheearth.terraplusplus.dataset.vector.geometry.VectorGeometry; import net.buildtheearth.terraplusplus.generator.CachedChunkData; @@ -8,40 +12,53 @@ import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.BVH; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; -import net.minecraft.util.math.ChunkPos; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; import java.util.Arrays; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; +import static net.daporkchop.lib.common.util.PorkUtil.*; + /** * @author DaPorkchop_ */ -public class OSMBaker implements IEarthDataBaker[]> { +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class DataBakerOSM implements IEarthDataBaker[]> { + protected final double paddingRadius; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public DataBakerOSM( + @JsonProperty(value = "paddingRadius", required = false) Double paddingRadius) { + this.paddingRadius = fallbackIfNull(paddingRadius, 16.0d); + } + @Override - public CompletableFuture[]> requestData(ChunkPos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException { + public CompletableFuture[]> requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo, PointArray2D sampledPoints) throws OutOfProjectionBoundsException { return datasets.>>getCustom(EarthGeneratorPipelines.KEY_DATASET_OSM_PARSED) - .getAsync(bounds.expand(16.0d).toCornerBB(datasets.projection(), false).toGeo()); + .getAsync(bounds.expand(this.paddingRadius).toCornerBB(datasets.projection(), false).toGeo(), pos.zoom()); } @Override - public void bake(ChunkPos pos, CachedChunkData.Builder builder, BVH[] regions) { + public void bake(TilePos pos, CachedChunkData.Builder builder, BVH[] regions) { if (regions == null) { //there's no data in this chunk... we're going to assume it's completely out of bounds Arrays.fill(builder.waterDepth(), (byte) (CachedChunkData.WATERDEPTH_TYPE_OCEAN | ~CachedChunkData.WATERDEPTH_TYPE_MASK)); return; } - int baseX = Coords.cubeToMinBlock(pos.x); - int baseZ = Coords.cubeToMinBlock(pos.z); - Bounds2d chunkBounds = Bounds2d.of(baseX, baseX + 16, baseZ, baseZ + 16); + int baseX = pos.blockX(); + int baseZ = pos.blockZ(); + Bounds2d chunkBounds = Bounds2d.of(baseX, baseX + pos.sizeBlocks(), baseZ, baseZ + pos.sizeBlocks()); Set elements = new TreeSet<>(); for (BVH region : regions) { region.forEachIntersecting(chunkBounds, elements::add); } - elements.forEach(element -> element.apply(builder, pos.x, pos.z, chunkBounds)); + elements.forEach(element -> element.apply(builder, pos.x(), pos.z(), pos.zoom(), chunkBounds)); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/data/TreeCoverBaker.java b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerTreeCover.java similarity index 71% rename from src/main/java/net/buildtheearth/terraplusplus/generator/data/TreeCoverBaker.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerTreeCover.java index 97306a9a..9e9f96df 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/data/TreeCoverBaker.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/data/DataBakerTreeCover.java @@ -1,15 +1,16 @@ package net.buildtheearth.terraplusplus.generator.data; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import net.buildtheearth.terraplusplus.dataset.IScalarDataset; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.generator.EarthGeneratorPipelines; import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; -import net.minecraft.util.math.ChunkPos; +import net.buildtheearth.terraplusplus.util.geo.pointarray.PointArray2D; -import java.util.Arrays; import java.util.concurrent.CompletableFuture; import static net.daporkchop.lib.common.math.PMath.*; @@ -17,13 +18,15 @@ /** * @author DaPorkchop_ */ -public class TreeCoverBaker implements IEarthDataBaker { +@JsonDeserialize +public final class DataBakerTreeCover implements IEarthDataBaker { public static final double TREE_AREA = 2.0d * 2.0d; //the surface area covered by an average tree public static final byte[] FALLBACK_TREE_DENSITY = new byte[16 * 16]; static { - Arrays.fill(FALLBACK_TREE_DENSITY, treeChance(50.0d)); + //TODO: figure out why i did this: + // Arrays.fill(FALLBACK_TREE_DENSITY, treeChance(50.0d)); } static byte treeChance(double value) { @@ -44,12 +47,12 @@ static byte treeChance(double value) { } @Override - public CompletableFuture requestData(ChunkPos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException { + public CompletableFuture requestData(TilePos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException { return datasets.getCustom(EarthGeneratorPipelines.KEY_DATASET_TREE_COVER).getAsync(boundsGeo, 16, 16); } @Override - public void bake(ChunkPos pos, CachedChunkData.Builder builder, double[] treeCover) { + public void bake(TilePos pos, CachedChunkData.Builder builder, double[] treeCover) { byte[] arr = new byte[16 * 16]; if (treeCover != null) { for (int i = 0; i < 16 * 16; i++) { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/data/IEarthDataBaker.java b/src/main/java/net/buildtheearth/terraplusplus/generator/data/IEarthDataBaker.java index effaee21..17797eaf 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/data/IEarthDataBaker.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/data/IEarthDataBaker.java @@ -1,10 +1,33 @@ package net.buildtheearth.terraplusplus.generator.data; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.generator.IEarthAsyncPipelineStep; +import static net.daporkchop.lib.common.util.PValidation.*; + /** * @author DaPorkchop_ */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonTypeIdResolver(IEarthDataBaker.TypeIdResolver.class) +@JsonDeserialize public interface IEarthDataBaker extends IEarthAsyncPipelineStep { + /** + * @return this {@link IEarthDataBaker}'s type ID + */ + default String typeId() { + String typeId = GlobalParseRegistries.GENERATOR_SETTINGS_DATA_BAKER.inverse().get(this.getClass()); + checkState(typeId != null, "unknown IEarthBiomeFilter implementation: %s", this.getClass()); + return typeId; + } + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.GENERATOR_SETTINGS_DATA_BAKER); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/data/NullIslandBaker.java b/src/main/java/net/buildtheearth/terraplusplus/generator/data/NullIslandBaker.java deleted file mode 100644 index c3c7fc1e..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/data/NullIslandBaker.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.buildtheearth.terraplusplus.generator.data; - -import net.buildtheearth.terraplusplus.generator.CachedChunkData; -import net.buildtheearth.terraplusplus.generator.EarthGenerator; -import net.buildtheearth.terraplusplus.generator.EarthGeneratorPipelines; -import net.buildtheearth.terraplusplus.generator.GeneratorDatasets; -import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; -import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; -import net.minecraft.init.Biomes; -import net.minecraft.util.math.ChunkPos; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -/** - * Sets the surface height at null island to 1. - * - * @author DaPorkchop_ - */ -public class NullIslandBaker implements IEarthDataBaker { - @Override - public CompletableFuture requestData(ChunkPos pos, GeneratorDatasets datasets, Bounds2d bounds, CornerBoundingBox2d boundsGeo) throws OutOfProjectionBoundsException { - return null; - } - - @Override - public void bake(ChunkPos pos, CachedChunkData.Builder builder, Void data) { - if (EarthGenerator.isNullIsland(pos.x, pos.z)) { - Arrays.fill(builder.surfaceHeight(), -1); - - byte[] trees = builder.getCustom(EarthGeneratorPipelines.KEY_DATA_TREE_COVER, null); - if (trees != null) { - Arrays.fill(trees, (byte) 0); - } - - if (((pos.x ^ (pos.x >> 31)) | (pos.z ^ (pos.z >> 31))) == 0) { - Arrays.fill(builder.biomes(), Biomes.FOREST); - } else { - Arrays.fill(builder.biomes(), Biomes.PLAINS); - } - - Arrays.fill(builder.waterDepth(), (byte) CachedChunkData.WATERDEPTH_DEFAULT); - } - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/CompatibilityEarthPopulators.java b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/CompatibilityEarthPopulators.java index 52805b6a..a8e2edea 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/CompatibilityEarthPopulators.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/CompatibilityEarthPopulators.java @@ -21,7 +21,7 @@ public class CompatibilityEarthPopulators { * Fires {@link PopulateCubeEvent.Pre}. */ public IEarthPopulator cubePopulatePre() { - return (world, random, pos, biome, datas) -> + return (world, random, pos, biome, datas, settings) -> MinecraftForge.EVENT_BUS.post(new PopulateCubeEvent.Pre(world, random, pos.getX(), pos.getY(), pos.getZ(), false)); } @@ -29,7 +29,7 @@ public IEarthPopulator cubePopulatePre() { * Fires {@link PopulateCubeEvent.Post}. */ public IEarthPopulator cubePopulatePost() { - return (world, random, pos, biome, datas) -> + return (world, random, pos, biome, datas, settings) -> MinecraftForge.EVENT_BUS.post(new PopulateCubeEvent.Post(world, random, pos.getX(), pos.getY(), pos.getZ(), false)); } @@ -37,6 +37,6 @@ public IEarthPopulator cubePopulatePost() { * Calls {@link CubeGeneratorsRegistry#generateWorld(World, Random, CubePos, Biome)}. */ public IEarthPopulator cubeGeneratorsRegistry() { - return (world, random, pos, biome, datas) -> CubeGeneratorsRegistry.generateWorld(world, random, pos, biome); + return (world, random, pos, biome, datas, settings) -> CubeGeneratorsRegistry.generateWorld(world, random, pos, biome); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/IEarthPopulator.java b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/IEarthPopulator.java index a80fc0d6..b960e497 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/IEarthPopulator.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/IEarthPopulator.java @@ -1,24 +1,49 @@ package net.buildtheearth.terraplusplus.generator.populate; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; import io.github.opencubicchunks.cubicchunks.api.util.CubePos; import io.github.opencubicchunks.cubicchunks.api.worldgen.populator.ICubicPopulator; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; import net.buildtheearth.terraplusplus.generator.CachedChunkData; +import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; import net.minecraft.world.World; import net.minecraft.world.biome.Biome; import java.util.Random; +import static net.daporkchop.lib.common.util.PValidation.*; + /** * A cube populator for earth terrain data. * * @author DaPorkchop_ * @see ICubicPopulator */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonTypeIdResolver(IEarthPopulator.TypeIdResolver.class) +@JsonDeserialize @FunctionalInterface public interface IEarthPopulator { /** * @param datas the {@link CachedChunkData} for the 2x2 column area being populated * @see ICubicPopulator#generate(World, Random, CubePos, Biome) */ - void populate(World world, Random random, CubePos pos, Biome biome, CachedChunkData[] datas); + void populate(World world, Random random, CubePos pos, Biome biome, CachedChunkData[] datas, EarthGeneratorSettings settings); + + /** + * @return this {@link IEarthPopulator}'s type ID + */ + default String typeId() { + String typeId = GlobalParseRegistries.GENERATOR_SETTINGS_POPULATOR.inverse().get(this.getClass()); + checkState(typeId != null, "unknown IEarthBiomeFilter implementation: %s", this.getClass()); + return typeId; + } + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.GENERATOR_SETTINGS_POPULATOR); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/BiomeDecorationPopulator.java b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorBiomeDecoration.java similarity index 63% rename from src/main/java/net/buildtheearth/terraplusplus/generator/populate/BiomeDecorationPopulator.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorBiomeDecoration.java index ae4a3462..f15b4f3c 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/BiomeDecorationPopulator.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorBiomeDecoration.java @@ -1,10 +1,10 @@ package net.buildtheearth.terraplusplus.generator.populate; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import io.github.opencubicchunks.cubicchunks.api.util.CubePos; import io.github.opencubicchunks.cubicchunks.api.worldgen.populator.ICubicPopulator; import io.github.opencubicchunks.cubicchunks.cubicgen.common.biome.CubicBiome; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -import lombok.NonNull; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; import net.minecraft.world.World; @@ -19,17 +19,28 @@ * * @author DaPorkchop_ */ -public class BiomeDecorationPopulator implements IEarthPopulator { - protected final Map populators = new Reference2ObjectOpenHashMap<>(ForgeRegistries.BIOMES.getKeys().size()); +@JsonDeserialize +public final class PopulatorBiomeDecoration implements IEarthPopulator { + protected transient final Map populators = new Reference2ObjectOpenHashMap<>(ForgeRegistries.BIOMES.getKeys().size()); + protected transient volatile boolean initialized = false; - public BiomeDecorationPopulator(@NonNull EarthGeneratorSettings settings) { - for (Biome biome : ForgeRegistries.BIOMES) { - this.populators.put(biome, CubicBiome.getCubic(biome).getDecorator(settings.customCubic())); + @Override + public void populate(World world, Random random, CubePos pos, Biome biome, CachedChunkData[] datas, EarthGeneratorSettings settings) { + if (!this.initialized) { + this.init(settings); } - } - @Override - public void populate(World world, Random random, CubePos pos, Biome biome, CachedChunkData[] datas) { this.populators.get(biome).generate(world, random, pos, biome); } + + private synchronized void init(EarthGeneratorSettings settings) { + if (this.initialized) { + return; + } + this.initialized = true; + + for (Biome biome : ForgeRegistries.BIOMES) { + this.populators.put(biome, CubicBiome.getCubic(biome).getDecorator(settings.customCubic())); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/SnowPopulator.java b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorSnow.java similarity index 86% rename from src/main/java/net/buildtheearth/terraplusplus/generator/populate/SnowPopulator.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorSnow.java index d4af650a..e46ddde1 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/SnowPopulator.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorSnow.java @@ -1,9 +1,11 @@ package net.buildtheearth.terraplusplus.generator.populate; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import io.github.opencubicchunks.cubicchunks.api.util.CubePos; import io.github.opencubicchunks.cubicchunks.api.world.ICube; import io.github.opencubicchunks.cubicchunks.api.world.ICubicWorld; import net.buildtheearth.terraplusplus.generator.CachedChunkData; +import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; import net.minecraft.init.Blocks; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -11,7 +13,8 @@ import java.util.Random; -public class SnowPopulator implements IEarthPopulator { +@JsonDeserialize +public final class PopulatorSnow implements IEarthPopulator { public static boolean canSnow(BlockPos pos, World world, boolean air) { if (!air && !world.isAirBlock(pos) || !world.canSnowAt(pos, false)) { return false; @@ -21,7 +24,7 @@ public static boolean canSnow(BlockPos pos, World world, boolean air) { } @Override - public void populate(World world, Random random, CubePos pos, Biome biome, CachedChunkData[] datas) { + public void populate(World world, Random random, CubePos pos, Biome biome, CachedChunkData[] datas, EarthGeneratorSettings settings) { if (canSnow(pos.getMaxBlockPos(), world, true)) { for (int i = 0, cx = 0; cx < 2; cx++) { for (int cz = 0; cz < 2; cz++) { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/TreePopulator.java b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorTrees.java similarity index 83% rename from src/main/java/net/buildtheearth/terraplusplus/generator/populate/TreePopulator.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorTrees.java index 44c877ac..6e08cf73 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/populate/TreePopulator.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/populate/PopulatorTrees.java @@ -1,14 +1,16 @@ package net.buildtheearth.terraplusplus.generator.populate; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.collect.ImmutableSet; import io.github.opencubicchunks.cubicchunks.api.util.CubePos; import io.github.opencubicchunks.cubicchunks.api.world.ICube; import io.github.opencubicchunks.cubicchunks.api.world.ICubicWorld; import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.buildtheearth.terraplusplus.generator.EarthGeneratorPipelines; -import net.buildtheearth.terraplusplus.generator.data.TreeCoverBaker; -import net.daporkchop.lib.common.ref.Ref; -import net.daporkchop.lib.common.ref.ThreadRef; +import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; +import net.buildtheearth.terraplusplus.generator.data.DataBakerTreeCover; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; @@ -20,7 +22,8 @@ import java.util.Random; import java.util.Set; -public class TreePopulator implements IEarthPopulator { +@JsonDeserialize +public final class PopulatorTrees implements IEarthPopulator { protected static final Set EXTRA_SURFACE = ImmutableSet.of( Blocks.SAND, Blocks.SANDSTONE, @@ -31,10 +34,10 @@ public class TreePopulator implements IEarthPopulator { Blocks.SNOW, Blocks.MYCELIUM); - protected static final Ref RNG_CACHE = ThreadRef.soft(() -> new byte[(ICube.SIZE >> 1) * (ICube.SIZE >> 1)]); + protected static final Cached RNG_CACHE = Cached.threadLocal(() -> new byte[(ICube.SIZE >> 1) * (ICube.SIZE >> 1)], ReferenceStrength.WEAK); @Override - public void populate(World world, Random random, CubePos pos, Biome biome, CachedChunkData[] datas) { + public void populate(World world, Random random, CubePos pos, Biome biome, CachedChunkData[] datas, EarthGeneratorSettings settings) { byte[] rng = RNG_CACHE.get(); for (int i = 0, cx = 0; cx < 2; cx++) { @@ -49,7 +52,7 @@ protected void populateColumn(World world, Random random, CubePos pos, Biome bio return; } - byte[] treeCover = data.getCustom(EarthGeneratorPipelines.KEY_DATA_TREE_COVER, TreeCoverBaker.FALLBACK_TREE_DENSITY); + byte[] treeCover = data.getCustom(EarthGeneratorPipelines.KEY_DATA_TREE_COVER, DataBakerTreeCover.FALLBACK_TREE_DENSITY); random.nextBytes(rng); for (int i = 0, dx = 0; dx < ICube.SIZE >> 1; dx++) { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/settings/GeneratorBlockSelector.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/GeneratorBlockSelector.java new file mode 100644 index 00000000..60957357 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/GeneratorBlockSelector.java @@ -0,0 +1,56 @@ +package net.buildtheearth.terraplusplus.generator.settings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.minecraft.block.state.IBlockState; + +import java.io.IOException; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; + +/** + * @author DaPorkchop_ + */ +public final class GeneratorBlockSelector extends JsonDeserializer> { + @Override + public Supplier deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + if (p.currentToken() == JsonToken.START_OBJECT) { + return ctxt.readValue(p, Single.class); + } else if (p.currentToken() == JsonToken.START_ARRAY) { + return ctxt.readValue(p, Multi.class); + } + throw new IllegalArgumentException(p.currentToken().name()); + } + + @RequiredArgsConstructor(onConstructor_ = { @JsonCreator(mode = JsonCreator.Mode.DELEGATING) }) + public static final class Single implements Supplier { + @NonNull + protected final IBlockState state; + + @Override + @JsonValue + public IBlockState get() { + return this.state; + } + } + + @RequiredArgsConstructor(onConstructor_ = { @JsonCreator(mode = JsonCreator.Mode.DELEGATING) }) + @Getter(onMethod_ = { @JsonValue }) + public static final class Multi implements Supplier { + @NonNull + protected final IBlockState[] states; + + @Override + public IBlockState get() { + return this.states[ThreadLocalRandom.current().nextInt(this.states.length)]; + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/settings/GeneratorTerrainSettings.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/GeneratorTerrainSettings.java new file mode 100644 index 00000000..debb9f7e --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/GeneratorTerrainSettings.java @@ -0,0 +1,56 @@ +package net.buildtheearth.terraplusplus.generator.settings; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import lombok.With; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; + +import java.util.function.Supplier; + +import static net.daporkchop.lib.common.util.PorkUtil.*; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@With +public class GeneratorTerrainSettings { + public static final GeneratorTerrainSettings DEFAULT = null; + + @NonNull + protected final Supplier fill; + + @NonNull + protected final Supplier water; + + @NonNull + protected final Supplier surface; + + @NonNull + protected final Supplier top; + + protected final boolean useCwgReplacers; + + public GeneratorTerrainSettings() { + this(null, null, null, null, null); + } + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GeneratorTerrainSettings( + @JsonProperty("fill") @JsonDeserialize(using = GeneratorBlockSelector.class) Supplier fill, + @JsonProperty("water") @JsonDeserialize(using = GeneratorBlockSelector.class) Supplier water, + @JsonProperty("surface") @JsonDeserialize(using = GeneratorBlockSelector.class) Supplier surface, + @JsonProperty("top") @JsonDeserialize(using = GeneratorBlockSelector.class) Supplier top, + @JsonProperty("useCwgReplacers") Boolean useCwgReplacers) { + this.fill = fill != null ? fill : new GeneratorBlockSelector.Single(Blocks.STONE.getDefaultState()); + this.water = water != null ? water : new GeneratorBlockSelector.Single(Blocks.WATER.getDefaultState()); + this.surface = surface != null ? surface : new GeneratorBlockSelector.Single(Blocks.DIRT.getDefaultState()); + this.top = top != null ? top : new GeneratorBlockSelector.Single(Blocks.GRASS.getDefaultState()); + this.useCwgReplacers = fallbackIfNull(useCwgReplacers, true); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/AbstractGeneratorOSMSettings.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/AbstractGeneratorOSMSettings.java new file mode 100644 index 00000000..ec565150 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/AbstractGeneratorOSMSettings.java @@ -0,0 +1,44 @@ +package net.buildtheearth.terraplusplus.generator.settings.osm; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; +import static net.daporkchop.lib.common.util.PorkUtil.*; + +/** + * @author DaPorkchop_ + */ +public abstract class AbstractGeneratorOSMSettings implements GeneratorOSMSettings { + @Getter(onMethod_ = {}, lazy = true) + private final OSMMapper mapper = this.initMapper(); + + @SneakyThrows(IOException.class) + protected OSMMapper initMapper() { + try (InputStream in = AbstractGeneratorOSMSettings.class.getResourceAsStream("osm.json5")) { + return uncheckedCast(JSON_MAPPER.readValue(in, OSMMapper.class)); + } + } + + protected abstract Map features(); + + /** + * The different OSM features that may be toggled. + * + * @author DaPorkchop_ + */ + @RequiredArgsConstructor + @Getter + public enum Feature { + ROADS(true); + + private final boolean isDefault; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettings.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettings.java new file mode 100644 index 00000000..f0fa84b1 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettings.java @@ -0,0 +1,31 @@ +package net.buildtheearth.terraplusplus.generator.settings.osm; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; +import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; + +/** + * Settings for OpenStreetMap used by {@link EarthGeneratorSettings}. + * + * @author DaPorkchop_ + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonTypeIdResolver(GeneratorOSMSettings.TypeIdResolver.class) +@JsonDeserialize +@FunctionalInterface +public interface GeneratorOSMSettings { + /** + * @return the {@link OSMMapper} to use, or {@code null} if OpenStreetMap generation should be completely disabled + */ + OSMMapper mapper(); + + final class TypeIdResolver extends GlobalParseRegistries.TypeIdResolver { + public TypeIdResolver() { + super(GlobalParseRegistries.GENERATOR_SETTINGS_OSM); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsAll.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsAll.java new file mode 100644 index 00000000..948678d0 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsAll.java @@ -0,0 +1,22 @@ +package net.buildtheearth.terraplusplus.generator.settings.osm; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import net.daporkchop.lib.common.function.PFunctions; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class GeneratorOSMSettingsAll extends AbstractGeneratorOSMSettings { + @Override + protected Map features() { + return Stream.of(Feature.values()).collect(Collectors.toMap(PFunctions.identity(), f -> true)); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsCustom.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsCustom.java new file mode 100644 index 00000000..a45b39a5 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsCustom.java @@ -0,0 +1,27 @@ +package net.buildtheearth.terraplusplus.generator.settings.osm; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; + +/** + * Implementation of {@link GeneratorOSMSettings} which allows use of a custom, user-configured {@link OSMMapper}. + * + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class GeneratorOSMSettingsCustom implements GeneratorOSMSettings { + protected final OSMMapper mapper; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public GeneratorOSMSettingsCustom( + @JsonProperty(value = "mapper", required = true) @NonNull OSMMapper mapper) { + this.mapper = mapper; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsDefault.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsDefault.java new file mode 100644 index 00000000..f4cbfa41 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsDefault.java @@ -0,0 +1,22 @@ +package net.buildtheearth.terraplusplus.generator.settings.osm; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import net.daporkchop.lib.common.function.PFunctions; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author DaPorkchop_ + */ +@Getter(onMethod_ = { @JsonGetter }) +@JsonDeserialize +public final class GeneratorOSMSettingsDefault extends AbstractGeneratorOSMSettings { + @Override + protected Map features() { + return Stream.of(Feature.values()).collect(Collectors.toMap(PFunctions.identity(), Feature::isDefault)); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsDisable.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsDisable.java new file mode 100644 index 00000000..f388bef4 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsDisable.java @@ -0,0 +1,16 @@ +package net.buildtheearth.terraplusplus.generator.settings.osm; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import net.buildtheearth.terraplusplus.dataset.osm.OSMMapper; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +public final class GeneratorOSMSettingsDisable implements GeneratorOSMSettings { + @Override + public OSMMapper mapper() { + return null; //return null to disable OpenStreetMap data entirely + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/config/condition/EqualDC.java b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsToggle.java similarity index 56% rename from src/main/java/net/buildtheearth/terraplusplus/config/condition/EqualDC.java rename to src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsToggle.java index f996f906..5a831953 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/config/condition/EqualDC.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/settings/osm/GeneratorOSMSettingsToggle.java @@ -1,24 +1,21 @@ -package net.buildtheearth.terraplusplus.config.condition; +package net.buildtheearth.terraplusplus.generator.settings.osm; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Getter; +import lombok.NonNull; import lombok.RequiredArgsConstructor; -import net.buildtheearth.terraplusplus.config.SingleProperty; + +import java.util.Map; /** * @author DaPorkchop_ */ @RequiredArgsConstructor(onConstructor_ = { @JsonCreator(mode = JsonCreator.Mode.DELEGATING) }) -@JsonDeserialize @Getter(onMethod_ = { @JsonValue }) -@SingleProperty -public class EqualDC implements DoubleCondition { - protected final double value; - - @Override - public boolean test(double value) { - return this.value == value; - } +@JsonDeserialize +public final class GeneratorOSMSettingsToggle extends AbstractGeneratorOSMSettings { + @NonNull + protected final Map features; } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/EqualEarthProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/EqualEarthProjection.java index b1bff730..b5727327 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/EqualEarthProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/EqualEarthProjection.java @@ -1,8 +1,7 @@ package net.buildtheearth.terraplusplus.projection; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.util.MathUtils; +import net.buildtheearth.terraplusplus.util.TerraUtils; /** * Implementation of the Equal Earth projection @@ -49,14 +48,14 @@ public double[] toGeo(double x, double y) { dx += 7 * A3 * (tpow *= thetasquare * thetasquare); //7 A3 t^6 dx += 9 * A4 * (tpow *= thetasquare); //9 A4 t^8 - return new double[]{ Math.toDegrees(x * dx * 3 / (2 * MathUtils.ROOT3 * Math.cos(theta))), - Math.toDegrees(Math.asin(Math.sin(theta) * 2 / MathUtils.ROOT3)) }; + return new double[]{ Math.toDegrees(x * dx * 3 / (2 * TerraUtils.ROOT3 * Math.cos(theta))), + Math.toDegrees(Math.asin(Math.sin(theta) * 2 / TerraUtils.ROOT3)) }; } @Override public double[] fromGeo(double longitude, double latitude) throws OutOfProjectionBoundsException { OutOfProjectionBoundsException.checkLongitudeLatitudeInRange(longitude, latitude); - double sintheta = MathUtils.ROOT3 * Math.sin(Math.toRadians(latitude)) / 2; + double sintheta = TerraUtils.ROOT3 * Math.sin(Math.toRadians(latitude)) / 2; double theta = Math.asin(sintheta); double tpow = theta; @@ -71,12 +70,12 @@ public double[] fromGeo(double longitude, double latitude) throws OutOfProjectio double costheta = Math.sqrt(1 - sintheta * sintheta); - return new double[]{ (2 * MathUtils.ROOT3 * Math.toRadians(longitude) * costheta / 3) / x, y }; + return new double[]{ (2 * TerraUtils.ROOT3 * Math.toRadians(longitude) * costheta / 3) / x, y }; } @Override - public double metersPerUnit() { - return TerraConstants.EARTH_CIRCUMFERENCE / (2 * this.bounds()[2]); + public double[] bounds() { + return GeographicProjection.super.bounds(); } @Override diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/EquirectangularProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/EquirectangularProjection.java index cabd2d89..72ace552 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/EquirectangularProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/EquirectangularProjection.java @@ -1,13 +1,16 @@ package net.buildtheearth.terraplusplus.projection; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import net.buildtheearth.terraplusplus.util.TerraConstants; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * Implements the equirectangular map projection, which applies no transformation at all. * x and y are therefore the same as longitude and latitude (in degrees). */ @JsonDeserialize -public class EquirectangularProjection implements GeographicProjection { +public class EquirectangularProjection implements GeographicProjection, GeographicProjection.FastProjectedCRS { /** * Converts map coordinates to geographic coordinates * @@ -21,6 +24,13 @@ public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException return new double[]{ x, y }; } + @Override + public Matrix2 toGeoDerivative(double x, double y) throws OutOfProjectionBoundsException { + OutOfProjectionBoundsException.checkLongitudeLatitudeInRange(x, y); + + return new Matrix2(1.0d, 0.0d, 0.0d, 1.0d); + } + /** * Converts geographic coordinates to map coordinates * @@ -34,16 +44,16 @@ public double[] fromGeo(double longitude, double latitude) throws OutOfProjectio return new double[]{ longitude, latitude }; } - /** - * Gives an estimation of the scale of this projection. - * This is just an estimation, as distortion is inevitable when projecting a sphere onto a flat surface, - * so this value varies from places to places in reality. - * - * @return an estimation of the scale of this projection - */ @Override - public double metersPerUnit() { - return 100000; + public Matrix2 fromGeoDerivative(double longitude, double latitude) throws OutOfProjectionBoundsException { + OutOfProjectionBoundsException.checkLongitudeLatitudeInRange(longitude, latitude); + + return new Matrix2(1.0d, 0.0d, 0.0d, 1.0d); + } + + @Override + public CoordinateReferenceSystem projectedCRS() { + return TerraConstants.TPP_GEO_CRS; } @Override diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/GeographicProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/GeographicProjection.java index 124e2dda..b25cde55 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/GeographicProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/GeographicProjection.java @@ -1,19 +1,52 @@ package net.buildtheearth.terraplusplus.projection; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; import lombok.NonNull; import lombok.SneakyThrows; -import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; import net.buildtheearth.terraplusplus.config.TypedDeserializer; import net.buildtheearth.terraplusplus.config.TypedSerializer; -import net.buildtheearth.terraplusplus.util.MathUtils; +import net.buildtheearth.terraplusplus.projection.epsg.EPSG3785; +import net.buildtheearth.terraplusplus.projection.epsg.EPSG4326; +import net.buildtheearth.terraplusplus.projection.epsg.EPSGProjection; +import net.buildtheearth.terraplusplus.projection.sis.SISProjectionWrapper; +import net.buildtheearth.terraplusplus.projection.sis.WKTStandard; +import net.buildtheearth.terraplusplus.projection.sis.WrappedProjectionOperationMethod; +import net.buildtheearth.terraplusplus.util.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraUtils; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.buildtheearth.terraplusplus.util.geo.GeographicCoordinates; +import net.buildtheearth.terraplusplus.util.geo.ProjectedCoordinates2d; +import net.daporkchop.lib.common.math.PMath; +import org.apache.sis.internal.referencing.AxisDirections; +import org.apache.sis.internal.referencing.ReferencingFactoryContainer; +import org.apache.sis.internal.simple.SimpleExtent; +import org.apache.sis.measure.Units; +import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.apache.sis.parameter.DefaultParameterValueGroup; +import org.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.IdentifiedObject; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.cs.AxisDirection; +import org.opengis.referencing.cs.CoordinateSystemAxis; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.util.FactoryException; import java.io.IOException; -import java.util.Collections; +import java.text.ParseException; import java.util.Map; +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; +import static net.daporkchop.lib.common.util.PValidation.*; + /** * Support for various projection types. *

@@ -23,7 +56,6 @@ * A projection as defined here is something that projects a point in the geographic space to a point of the projected space (and vice versa). *

* All geographic coordinates are in degrees. - * */ @JsonDeserialize(using = GeographicProjection.Deserializer.class) @JsonSerialize(using = GeographicProjection.Serializer.class) @@ -40,9 +72,22 @@ static GeographicProjection parse(@NonNull String config) { * @param y - y map coordinate * @return {longitude, latitude} in degrees * @throws OutOfProjectionBoundsException if the specified point on the projected space cannot be mapped to a point of the geographic space + * @see #toGeo(ProjectedCoordinates2d) */ double[] toGeo(double x, double y) throws OutOfProjectionBoundsException; + /** + * Converts map coordinates to geographic coordinates + * + * @param coordinates the map coordinates + * @return {longitude, latitude} in degrees + * @throws OutOfProjectionBoundsException if the specified point on the projected space cannot be mapped to a point of the geographic space + */ + default GeographicCoordinates toGeo(@NonNull ProjectedCoordinates2d coordinates) throws OutOfProjectionBoundsException { + double[] geo = this.toGeo(coordinates.x(), coordinates.y()); + return GeographicCoordinates.fromLonLatDegrees(geo[0], geo[1]); + } + /** * Converts geographic coordinates to map coordinates * @@ -54,13 +99,24 @@ static GeographicProjection parse(@NonNull String config) { double[] fromGeo(double longitude, double latitude) throws OutOfProjectionBoundsException; /** - * Gives an estimation of the scale of this projection. - * This is just an estimation, as distortion is inevitable when projecting a sphere onto a flat surface, - * so this value varies from places to places in reality. + * Converts geographic coordinates to map coordinates * - * @return an estimation of the scale of this projection + * @param coordinates the map coordinates + * @return {x, y} map coordinates + * @throws OutOfProjectionBoundsException if the specified point on the geographic space cannot be mapped to a point of the projected space */ - double metersPerUnit(); + default ProjectedCoordinates2d fromGeo(@NonNull GeographicCoordinates coordinates) throws OutOfProjectionBoundsException { + double[] map = this.fromGeo(coordinates.longitudeDegrees(), coordinates.latitudeDegrees()); + return ProjectedCoordinates2d.ofXY(map[0], map[1]); + } + + default Matrix2 toGeoDerivative(double x, double y) throws OutOfProjectionBoundsException { + return GeographicProjectionHelper.defaultDerivative(this, x, y, false); + } + + default Matrix2 fromGeoDerivative(double longitude, double latitude) throws OutOfProjectionBoundsException { + return GeographicProjectionHelper.defaultDerivative(this, longitude, latitude, true); + } /** * Indicates the minimum and maximum X and Y coordinates on the projected space. @@ -69,32 +125,38 @@ static GeographicProjection parse(@NonNull String config) { */ default double[] bounds() { try { + double[] boundsGeo = this.boundsGeo(); + //get max in by using extreme coordinates - double[] bounds = { - this.fromGeo(-180, 0)[0], - this.fromGeo(0, -90)[1], - this.fromGeo(180, 0)[0], - this.fromGeo(0, 90)[1] - }; - - if (bounds[0] > bounds[2]) { - double t = bounds[0]; - bounds[0] = bounds[2]; - bounds[2] = t; - } + double[] bounds = null; - if (bounds[1] > bounds[3]) { - double t = bounds[1]; - bounds[1] = bounds[3]; - bounds[3] = t; + double[] blendFactors = { 0.0d, 0.5d, 1.0d }; + for (double flon : blendFactors) { + for (double flat : blendFactors) { + double[] point = this.fromGeo(PMath.lerp(boundsGeo[0], boundsGeo[2], flon), PMath.lerp(boundsGeo[1], boundsGeo[3], flat)); + if (bounds == null) { + bounds = new double[]{ point[0], point[1], point[0], point[1] }; + } else { + bounds[0] = Math.min(bounds[0], point[0]); + bounds[1] = Math.min(bounds[1], point[1]); + bounds[2] = Math.max(bounds[2], point[0]); + bounds[3] = Math.max(bounds[3], point[1]); + } + } } return bounds; } catch (OutOfProjectionBoundsException e) { - return new double[]{ 0, 0, 1, 1 }; + throw new IllegalStateException(this.toString()); } } + default double[] boundsGeo() { + return new double[]{ + -180, -90, 180, 90 + }; + } + /** * Indicates whether or not the north pole is projected to the north of the south pole on the projected space, * assuming Minecraft's coordinate system cardinal directions for the projected space (north is negative Z). @@ -202,11 +264,11 @@ default float azimuth(double x, double y, float angle, double d) throws OutOfPro double y2 = y + d * Math.cos(Math.toRadians(angle)); double[] geo1 = this.toGeo(x, y); double[] geo2 = this.toGeo(x2, y2); - MathUtils.toRadians(geo1); - MathUtils.toRadians(geo2); + TerraUtils.toRadians(geo1); + TerraUtils.toRadians(geo2); double dlon = geo2[0] - geo1[0]; double dlat = geo2[1] - geo1[1]; - double a = Math.toDegrees(Math.atan2(dlat, dlon*Math.cos(geo1[1]))); + double a = Math.toDegrees(Math.atan2(dlat, dlon * Math.cos(geo1[1]))); a = 90 - a; if (a < 0) { a += 360; @@ -230,11 +292,50 @@ default float azimuth(double x, double y, float angle) throws OutOfProjectionBou return this.azimuth(x, y, angle, 1E-5); } + default ParameterValueGroup parameters() { + return new ParameterBuilder().addName("empty").createGroup().createValue(); + } + + /** + * @deprecated use {@link SISHelper#projectedCRS(GeographicProjection)} + */ + @Deprecated + @SneakyThrows(FactoryException.class) + default CoordinateReferenceSystem projectedCRS() { + ObjectNode selfRootNode = TerraConstants.JSON_MAPPER.valueToTree(this); + + double[] boundsGeo = this.boundsGeo(); + + ReferencingFactoryContainer factories = SISHelper.factories(); + + CoordinateSystemAxis[] axes = { + factories.getCSFactory().createCoordinateSystemAxis(ImmutableMap.of(IdentifiedObject.NAME_KEY, "Easting"), "X", AxisDirection.EAST, Units.METRE), + factories.getCSFactory().createCoordinateSystemAxis(ImmutableMap.of(IdentifiedObject.NAME_KEY, "Northing"), "Y", AxisDirection.NORTH, Units.METRE), + }; + + DefaultParameterValueGroup parameters = new DefaultParameterValueGroup(factories.getMathTransformFactory().getDefaultParameters("Terra++ Internal Projection")); + parameters.getOrCreate(WrappedProjectionOperationMethod.PARAMETER_TYPE).setValue(selfRootNode.fieldNames().next()); + parameters.getOrCreate(WrappedProjectionOperationMethod.PARAMETER_JSON_ARGS).setValue(selfRootNode.elements().next().toString()); + + return factories.getCRSFactory().createProjectedCRS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, "WGS 84 / Reversed Axis Order / Terra++ Wrapped GeographicProjection: " + selfRootNode, + CoordinateOperation.DOMAIN_OF_VALIDITY_KEY, new SimpleExtent(new DefaultGeographicBoundingBox(boundsGeo[0], boundsGeo[2], boundsGeo[1], boundsGeo[3]), null, null)), + TPP_GEO_CRS, + factories.getCoordinateOperationFactory().createDefiningConversion( + ImmutableMap.of(IdentifiedObject.NAME_KEY, "Terra++ Wrapped GeographicProjection: " + selfRootNode), + factories.getCoordinateOperationFactory().getOperationMethod("Terra++ Internal Projection"), + parameters), + factories.getCSFactory().createCartesianCS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, AxisDirections.appendTo(new StringBuilder("Cartesian CS"), axes)), + axes[0], axes[1])); + } + /** - * @return any additional configuration properties used by this projection + * Marker interface for {@link GeographicProjection} implementations which indicates that {@link #projectedCRS()} is fast and its result does not need to be cached. + * + * @author DaPorkchop_ */ - default Map properties() { - return Collections.emptyMap(); + interface FastProjectedCRS { } class Deserializer extends TypedDeserializer { @@ -242,6 +343,49 @@ class Deserializer extends TypedDeserializer { protected Map> registry() { return GlobalParseRegistries.PROJECTIONS; } + + @Override + public GeographicProjection deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + if (p.currentToken() == JsonToken.VALUE_STRING) { + return this.parseFromString(p.getValueAsString()); + } else { + return super.deserialize(p, ctxt); + } + } + + @SneakyThrows(ParseException.class) + private GeographicProjection parseFromString(@NonNull String s) { + int colonIndex = s.indexOf(':'); + checkArg(colonIndex >= 0 && colonIndex + 1 != s.length(), "unsupported projection: %s", s); + + String type = s.substring(0, colonIndex); + String arg = s.substring(colonIndex + 1); + switch (type) { + case "EPSG": { //TODO + switch (arg) { + case "3785": + case "3857": //TODO: EPSG:3857 actually uses the WGS84 ellipsoid rather than a sphere + return new EPSG3785(); + case "4326": + return new EPSG4326(); + } + + CharSequence wkt = EPSGProjection.registry(WKTStandard.WKT2_2015).get(Integer.parseInt(arg)); + if (wkt != null) { + return new SISProjectionWrapper(WKTStandard.WKT2_2015, wkt.toString()); + } + break; + } + case "WKT2": + if (arg.startsWith("2015:")) { + return new SISProjectionWrapper(WKTStandard.WKT2_2015, arg.substring("2015:".length())); + } else { + throw new IllegalArgumentException("expected format: 'WKT2:2015:'"); + } + } + + throw new IllegalArgumentException("unsupported projection: " + s); + } } class Serializer extends TypedSerializer { diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/GeographicProjectionHelper.java b/src/main/java/net/buildtheearth/terraplusplus/projection/GeographicProjectionHelper.java new file mode 100644 index 00000000..be370268 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/GeographicProjectionHelper.java @@ -0,0 +1,64 @@ +package net.buildtheearth.terraplusplus.projection; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import org.apache.sis.referencing.operation.matrix.Matrix2; + +/** + * Static helper methods, intended for use by implementations of {@link GeographicProjection}. + * + * @author DaPorkchop_ + */ +@UtilityClass +public class GeographicProjectionHelper { + public static final double DEFAULT_DERIVATIVE_DELTA = 1e-9d; + + public static Matrix2 defaultDerivative(@NonNull GeographicProjection projection, double x, double y, boolean fromGeo) throws OutOfProjectionBoundsException { + return defaultDerivative(projection, x, y, fromGeo, DEFAULT_DERIVATIVE_DELTA); + } + + public static Matrix2 defaultDerivative(@NonNull GeographicProjection projection, double x, double y, boolean fromGeo, double d) throws OutOfProjectionBoundsException { + double[] result00 = project(projection, x, y, fromGeo); + + double x00 = result00[0]; + double y00 = result00[1]; + + double inverseD = 1.0d / d; + + double f01; + double[] result01; + try { + f01 = inverseD; + result01 = project(projection, x, y + d, fromGeo); + } catch (OutOfProjectionBoundsException e) { + f01 = -inverseD; + result01 = project(projection, x, y - d, fromGeo); + } + + double x01 = result01[0]; + double y01 = result01[1]; + + double f10; + double[] result10; + try { + f10 = inverseD; + result10 = project(projection, x + d, y, fromGeo); + } catch (OutOfProjectionBoundsException e) { + f10 = -inverseD; + result10 = project(projection, x - d, y, fromGeo); + } + + double x10 = result10[0]; + double y10 = result10[1]; + + return new Matrix2( + (x10 - x00) * f10, + (x01 - x00) * f01, + (y10 - y00) * f10, + (y01 - y00) * f01); + } + + public static double[] project(@NonNull GeographicProjection projection, double x, double y, boolean fromGeo) throws OutOfProjectionBoundsException { + return fromGeo ? projection.fromGeo(x, y) : projection.toGeo(x, y); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/OutOfProjectionBoundsException.java b/src/main/java/net/buildtheearth/terraplusplus/projection/OutOfProjectionBoundsException.java index a1d4bf43..587d65b2 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/OutOfProjectionBoundsException.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/OutOfProjectionBoundsException.java @@ -1,15 +1,18 @@ package net.buildtheearth.terraplusplus.projection; -public final class OutOfProjectionBoundsException extends Exception { - private static final OutOfProjectionBoundsException INSTANCE = new OutOfProjectionBoundsException(false); +import org.apache.sis.referencing.operation.projection.ProjectionException; +import org.opengis.referencing.operation.MathTransform; - private static final boolean FAST = Boolean.parseBoolean(System.getProperty("terraplusplus.fastExcept", "true")); +public final class OutOfProjectionBoundsException extends ProjectionException { + private static final OutOfProjectionBoundsException INSTANCE = new OutOfProjectionBoundsException(); + + public static final boolean FAST = Boolean.parseBoolean(System.getProperty("terraplusplus.fastExcept", "true")); public static OutOfProjectionBoundsException get() { if (FAST) { return INSTANCE; } else { - return new OutOfProjectionBoundsException(true); + return new OutOfProjectionBoundsException(); } } @@ -35,7 +38,18 @@ public static void checkLongitudeLatitudeInRange(double longitude, double latitu checkInRange(longitude, latitude, 180, 90); } - private OutOfProjectionBoundsException(boolean flag) { - super(null, null, flag, flag); + @Override + public Throwable fillInStackTrace() { + if (INSTANCE != null) { //if INSTANCE is null, we're still in the class constructor + super.fillInStackTrace(); + } + return this; + } + + @Override + public void setLastCompletedTransform(MathTransform transform) { + if (this != INSTANCE) { + super.setLastCompletedTransform(transform); + } } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/SinusoidalProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/SinusoidalProjection.java index d9cad0ca..defd84a5 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/SinusoidalProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/SinusoidalProjection.java @@ -1,7 +1,19 @@ package net.buildtheearth.terraplusplus.projection; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import net.buildtheearth.terraplusplus.TerraConstants; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.projection.sis.AbstractOperationMethod; +import net.buildtheearth.terraplusplus.projection.sis.AbstractSISMigratedGeographicProjection; +import net.buildtheearth.terraplusplus.projection.sis.transform.AbstractFromGeoMathTransform2D; +import net.buildtheearth.terraplusplus.projection.sis.transform.AbstractToGeoMathTransform2D; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.transform.ContextualParameters; +import org.opengis.parameter.InvalidParameterNameException; +import org.opengis.parameter.InvalidParameterValueException; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.TransformException; /** * Implementation of the Sinusoidal projection. @@ -9,25 +21,131 @@ * @see Wikipedia's article on the sinusoidal projection */ @JsonDeserialize -public class SinusoidalProjection implements GeographicProjection { +public class SinusoidalProjection extends AbstractSISMigratedGeographicProjection { @Override public double[] toGeo(double x, double y) { return new double[]{ x / Math.cos(Math.toRadians(y)), y }; } + @Override + public Matrix2 toGeoDerivative(double x, double y) throws OutOfProjectionBoundsException { + //https://www.wolframalpha.com/input?i=d%2Fdx+x+%2F+cos%28y+%2F+180+*+pi%29 + double m00 = (2.0d * Math.cos(Math.toRadians(y))) / (Math.cos(Math.toRadians(y * 2.0d)) + 1.0d); + + //https://www.wolframalpha.com/input?i=d%2Fdy+x+%2F+cos%28y+%2F+180+*+pi%29 + double m01 = (Math.PI * x * Math.sin(Math.toRadians(y))) / (90.0d * (Math.cos(Math.toRadians(y * 2.0d)) + 1.0d)); + double m10 = 0.0d; + double m11 = 1.0d; + + return new Matrix2(m00, m01, m10, m11); + } + @Override public double[] fromGeo(double longitude, double latitude) throws OutOfProjectionBoundsException { - OutOfProjectionBoundsException.checkLongitudeLatitudeInRange(longitude, latitude); + OutOfProjectionBoundsException.checkLongitudeLatitudeInRange(longitude, latitude); return new double[]{ longitude * Math.cos(Math.toRadians(latitude)), latitude }; } @Override - public double metersPerUnit() { - return TerraConstants.EARTH_CIRCUMFERENCE / 360.0; //gotta make good on that exact area + public Matrix2 fromGeoDerivative(double longitude, double latitude) throws OutOfProjectionBoundsException { + OutOfProjectionBoundsException.checkLongitudeLatitudeInRange(longitude, latitude); + + //https://www.wolframalpha.com/input?i=d%2Fdx+x+*+cos%28y+%2F+180+*+pi%29 + double m00 = Math.cos(Math.toRadians(latitude)); + + //https://www.wolframalpha.com/input?i=d%2Fdy+x+*+cos%28y+%2F+180+*+pi%29 + double m01 = -Math.toRadians(longitude) * Math.sin(Math.toRadians(latitude)); + double m10 = 0.0d; + double m11 = 1.0d; + + return new Matrix2(m00, m01, m10, m11); + } + + @Override + public double[] bounds() { + return this.boundsGeo(); } @Override public String toString() { return "Sinusoidal"; } + + public static final class OperationMethod extends AbstractOperationMethod.ForLegacyProjection { + public OperationMethod() { + super("Sinusoidal"); + } + + @Override + protected AbstractFromGeoMathTransform2D createBaseTransform(ParameterValueGroup parameters) throws InvalidParameterNameException, ParameterNotFoundException, InvalidParameterValueException { + return new FromGeo(parameters); + } + } + + private static final class FromGeo extends AbstractFromGeoMathTransform2D { + public FromGeo(@NonNull ParameterValueGroup parameters) { + super(parameters, new ToGeo(parameters)); + } + + @Override + protected void configureMatrices(ContextualParameters contextualParameters, MatrixSIS normalize, MatrixSIS denormalize) { + //degrees -> radians + contextualParameters.normalizeGeographicInputs(0.0d); + contextualParameters.denormalizeGeographicOutputs(0.0d); + } + + @Override + public Matrix2 transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws TransformException { + double lon = srcPts[srcOff + 0]; + double lat = srcPts[srcOff + 1]; + + if (dstPts != null) { + dstPts[dstOff + 0] = lon * Math.cos(lat); + dstPts[dstOff + 1] = lat; + } + if (!derivate) { + return null; + } + + //https://www.wolframalpha.com/input?i=d%2Fdx+x+*+cos%28y%29 + double m00 = Math.cos(lat); + + //https://www.wolframalpha.com/input?i=d%2Fdy+x+*+cos%28y%29 + double m01 = -lon * Math.sin(lat); + double m10 = 0.0d; + double m11 = 1.0d; + + return new Matrix2(m00, m01, m10, m11); + } + } + + private static final class ToGeo extends AbstractToGeoMathTransform2D { + public ToGeo(@NonNull ParameterValueGroup parameters) { + super(parameters); + } + + @Override + public Matrix2 transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws TransformException { + double x = srcPts[srcOff + 0]; + double y = srcPts[srcOff + 1]; + + if (dstPts != null) { + dstPts[dstOff + 0] = x / Math.cos(y); + dstPts[dstOff + 1] = y; + } + if (!derivate) { + return null; + } + + //https://www.wolframalpha.com/input?i=d%2Fdx+x+%2F+cos%28y%29 + double m00 = (2.0d * Math.cos(y)) / (Math.cos(y * 2.0d) + 1.0d); + + //https://www.wolframalpha.com/input?i=d%2Fdy+x+%2F+cos%28y%29 + double m01 = (2.0d * x * Math.sin(y)) / ((Math.cos(y * 2.0d) + 1.0d)); + double m10 = 0.0d; + double m11 = 1.0d; + + return new Matrix2(m00, m01, m10, m11); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/BTEDymaxionProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/BTEDymaxionProjection.java index 644f4def..344c9421 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/BTEDymaxionProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/BTEDymaxionProjection.java @@ -1,8 +1,29 @@ package net.buildtheearth.terraplusplus.projection.dymaxion; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.NonNull; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.MathUtils; +import net.buildtheearth.terraplusplus.projection.sis.AbstractOperationMethod; +import net.buildtheearth.terraplusplus.projection.sis.transform.AbstractFromGeoMathTransform2D; +import net.buildtheearth.terraplusplus.util.TerraUtils; +import net.buildtheearth.terraplusplus.util.math.matrix.TMatrices; +import net.daporkchop.lib.common.reference.cache.Cached; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.transform.ContextualParameters; +import org.apache.sis.referencing.operation.transform.IterationStrategy; +import org.opengis.parameter.InvalidParameterNameException; +import org.opengis.parameter.InvalidParameterValueException; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.TransformException; + +import javax.vecmath.Vector2d; +import javax.vecmath.Vector3d; +import java.util.Arrays; + +import static net.buildtheearth.terraplusplus.util.TerraUtils.*; /** * Implementation of the BTE modified Dynmaxion projection. @@ -19,7 +40,7 @@ public class BTEDymaxionProjection extends ConformalDynmaxionProjection { protected static final double BERING_X = -0.3420420960118339;//-0.3282152608138795; protected static final double BERING_Y = -0.322211064085279;//-0.3281491467713469; protected static final double ARCTIC_Y = -0.2;//-0.3281491467713469; - protected static final double ARCTIC_M = (ARCTIC_Y - MathUtils.ROOT3 * ARC / 4) / (BERING_X - -0.5 * ARC); + protected static final double ARCTIC_M = (ARCTIC_Y - TerraUtils.ROOT3 * ARC / 4) / (BERING_X - -0.5 * ARC); protected static final double ARCTIC_B = ARCTIC_Y - ARCTIC_M * BERING_X; protected static final double ALEUTIAN_Y = -0.5000446805492526;//-0.5127463765943157; protected static final double ALEUTIAN_XL = -0.5149231279757507;//-0.4957832938238718; @@ -27,15 +48,17 @@ public class BTEDymaxionProjection extends ConformalDynmaxionProjection { protected static final double ALEUTIAN_M = (BERING_Y - ALEUTIAN_Y) / (BERING_X - ALEUTIAN_XR); protected static final double ALEUTIAN_B = BERING_Y - ALEUTIAN_M * BERING_X; + protected static final Cached TMP_LENGTH2_ARRAY_CACHE = Cached.threadLocal(() -> new double[2]); + @Override public double[] fromGeo(double longitude, double latitude) throws OutOfProjectionBoundsException { double[] c = super.fromGeo(longitude, latitude); double x = c[0]; double y = c[1]; - boolean easia = this.isEurasianPart(x, y); + boolean easia = isEurasianPart(x, y); - y -= 0.75 * ARC * MathUtils.ROOT3; + y -= 0.75 * ARC * TerraUtils.ROOT3; if (easia) { x += ARC; @@ -55,19 +78,19 @@ public double[] fromGeo(double longitude, double latitude) throws OutOfProjectio @Override public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException { - boolean easia; - if (y < 0) { - easia = x > 0; - } else if (y > ARC / 2) { - easia = x > -MathUtils.ROOT3 * ARC / 2; - } else { - easia = y * -MathUtils.ROOT3 < x; - } - double t = x; x = -y; y = t; + boolean easia; + if (-x < 0) { + easia = y > 0; + } else if (-x > ARC / 2) { + easia = y > -TerraUtils.ROOT3 * ARC / 2; + } else { + easia = -x * -TerraUtils.ROOT3 < y; + } + if (easia) { t = x; x = COS_THETA * x + SIN_THETA * y; @@ -78,17 +101,17 @@ public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException x += ARC; } - y += 0.75 * ARC * MathUtils.ROOT3; + y += 0.75 * ARC * TerraUtils.ROOT3; //check to make sure still in right part - if (easia != this.isEurasianPart(x, y)) { + if (easia != isEurasianPart(x, y)) { throw OutOfProjectionBoundsException.get(); } return super.toGeo(x, y); } - protected boolean isEurasianPart(double x, double y) { + private static boolean isEurasianPart(double x, double y) { //catch vast majority of cases in not near boundary if (x > 0) { @@ -98,7 +121,7 @@ protected boolean isEurasianPart(double x, double y) { return true; } - if (y > MathUtils.ROOT3 * ARC / 4) //above arctic ocean + if (y > TerraUtils.ROOT3 * ARC / 4) //above arctic ocean { return x < 0; } @@ -122,11 +145,264 @@ protected boolean isEurasianPart(double x, double y) { @Override public double[] bounds() { - return new double[]{ -1.5 * ARC * MathUtils.ROOT3, -1.5 * ARC, 3 * ARC, MathUtils.ROOT3 * ARC }; //TODO: 3*ARC is prly to high + return new double[]{ -1.5 * ARC * TerraUtils.ROOT3, -1.5 * ARC, 3 * ARC, TerraUtils.ROOT3 * ARC }; //TODO: 3*ARC is prly to high } @Override public String toString() { return "BuildTheEarth Conformal Dymaxion"; } -} \ No newline at end of file + + public static final class OperationMethod extends AbstractOperationMethod.ForLegacyProjection { + public OperationMethod() { + super("BuildTheEarth Conformal Dymaxion"); + } + + @Override + protected AbstractFromGeoMathTransform2D createBaseTransform(ParameterValueGroup parameters) throws InvalidParameterNameException, ParameterNotFoundException, InvalidParameterValueException { + return new FromGeo<>(parameters, new ToGeo<>(parameters, ConformalDynmaxionProjection.TRANSFORM_RESOURCE_CACHE), ConformalDynmaxionProjection.TRANSFORM_RESOURCE_CACHE); + } + } + + protected static class FromGeo extends ConformalDynmaxionProjection.FromGeo { + private static final Matrix2 EURASIA_ROTATE_MATRIX = new Matrix2(COS_THETA, -SIN_THETA, SIN_THETA, COS_THETA); + + public FromGeo(@NonNull ParameterValueGroup parameters, @NonNull ToGeo toGeo, @NonNull Cached cacheCache) { + super(parameters, toGeo, cacheCache); + } + + @Override + protected void configureMatrices(ContextualParameters contextualParameters, MatrixSIS normalize, MatrixSIS denormalize) { + super.configureMatrices(contextualParameters, normalize, denormalize); + + //c[0] = y; + //c[1] = -x; + denormalize.setMatrix(Matrices.createDimensionSelect(2, new int[]{ 1, 0 }).multiply(denormalize)); + denormalize.convertAfter(1, -1L, null); + } + + @Override + public Matrix2 transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws TransformException { + if (derivate && dstPts == null) { //the derivative was requested, we need to get the projected coordinates as well + dstPts = TMP_LENGTH2_ARRAY_CACHE.get(); + dstOff = 0; + } + + Matrix2 derivative = super.transform(srcPts, srcOff, dstPts, dstOff, derivate); + + if (dstPts != null) { + double x = dstPts[dstOff + 0]; + double y = dstPts[dstOff + 1]; + + boolean eurasia = isEurasianPart(x, y); + + y -= 0.75d * ARC * TerraUtils.ROOT3; + + if (eurasia) { + double x0 = x + ARC; + double y0 = y; + x = COS_THETA * x0 - SIN_THETA * y0; + y = SIN_THETA * x0 + COS_THETA * y0; + + if (derivative != null) { + TMatrices.multiplyFast(EURASIA_ROTATE_MATRIX, derivative.clone(), derivative); + } + } else { + x -= ARC; + + //all the offsets are by a constant factor, so the derivative isn't affected + } + + dstPts[dstOff + 0] = x; + dstPts[dstOff + 1] = y; + } + + return derivative; + } + + @Override + public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException { + try { + //do the main transform first, we'll transform the results afterwards + super.transform(srcPts, srcOff, dstPts, dstOff, numPts); + } finally { + //do the final transformation, leaving any invalid points as-is. + // (if there are invalid points in the output, we assume an exception has already been thrown and will be rethrown + // once the finally block ends) + transformPostProcess(dstPts, dstOff, numPts); + } + } + + private static void transformPostProcess(double[] dstPts, int dstOff, int numPts) { + for (; --numPts >= 0; dstOff += 2) { + double x = dstPts[dstOff + 0]; + double y = dstPts[dstOff + 1]; + + if (isInvalidCoordinates(x, y)) { //this point is already invalid, we can assume an exception is already being thrown + continue; + } + + // + // the following is the same algorithm as above, but copied here for performance + // + + boolean eurasia = isEurasianPart(x, y); + + y -= 0.75d * ARC * TerraUtils.ROOT3; + + if (eurasia) { + double x0 = x + ARC; + double y0 = y; + x = COS_THETA * x0 - SIN_THETA * y0; + y = SIN_THETA * x0 + COS_THETA * y0; + } else { + x -= ARC; + + //all the offsets are by a constant factor, so the derivative isn't affected + } + + dstPts[dstOff + 0] = x; + dstPts[dstOff + 1] = y; + } + } + } + + protected static class ToGeo extends ConformalDynmaxionProjection.ToGeo { + private static final Matrix2 EURASIA_ROTATE_MATRIX = new Matrix2(COS_THETA, SIN_THETA, -SIN_THETA, COS_THETA); + + public ToGeo(@NonNull ParameterValueGroup parameters, @NonNull Cached cacheCache) { + super(parameters, cacheCache); + } + + @Override + public Matrix2 transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws TransformException { + double x = srcPts[srcOff + 0]; + double y = srcPts[srcOff + 1]; + + if (isInvalidCoordinates(x, y)) { //propagate NaN coordinates immediately + throw this.getSingleExceptionForSelf(); + } + + boolean eurasia; + if (x > 0.0d) { + eurasia = y > 0.0d; + } else if (x < -ARC / 2.0d) { + eurasia = y > -TerraUtils.ROOT3 * ARC / 2.0d; + } else { + eurasia = x * TerraUtils.ROOT3 < y; + } + + if (eurasia) { + double x0 = x; + double y0 = y; + x = SIN_THETA * y0 + COS_THETA * x0 - ARC; + y = COS_THETA * y0 - SIN_THETA * x0; + } else { + x += ARC; + } + + y += 0.75d * ARC * TerraUtils.ROOT3; + + //check to make sure still in right part + if (eurasia != isEurasianPart(x, y)) { + throw this.getSingleExceptionForSelf(); + } + + srcPts = TMP_LENGTH2_ARRAY_CACHE.get(); + srcOff = 0; + + srcPts[srcOff + 0] = x; + srcPts[srcOff + 1] = y; + + Matrix2 derivative = super.transform(srcPts, srcOff, dstPts, dstOff, derivate); + + if (eurasia && derivative != null) { + TMatrices.multiplyFast(derivative, EURASIA_ROTATE_MATRIX, derivative); + } + + return derivative; + } + + @Override + public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException { + //pre-transform the points, storing the results in the destination array + transformPreProcess(srcPts, srcOff, dstPts, dstOff, numPts); + + //do the actual dymaxion transformation on the points in-place. if any points were out-of-bounds before, they're now NaN values in the + // array and will cause this to throw an exception. + super.transform(dstPts, dstOff, dstPts, dstOff, numPts); + } + + private static void transformPreProcess(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) { + int srcInc = 2; + int dstInc = 2; + + //noinspection ArrayEquality + if (srcPts == dstPts) { + switch (IterationStrategy.suggest(srcOff, 2, dstOff, 2, numPts)) { + case ASCENDING: { + break; + } + case DESCENDING: { + srcOff += 2 * (numPts - 1); + dstOff += 2 * (numPts - 1); + srcInc -= 4; + dstInc -= 4; + break; + } + default: { + //TODO: use array allocator for this + srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts * 2); + srcOff = 0; + break; + } + } + } + + for (; --numPts >= 0; srcOff += srcInc, dstOff += dstInc) { + double x = srcPts[srcOff + 0]; + double y = srcPts[srcOff + 1]; + + if (isInvalidCoordinates(x, y)) { //the point is invalid, write NaN values into the destination array + // we assume that the exception will be thrown later when calling super.transform() when it detects NaN values + fillNaN(dstPts, dstOff); + continue; + } + + // + // the following is the same algorithm as above, but copied here for performance + // + + boolean eurasia; + if (x > 0.0d) { + eurasia = y > 0.0d; + } else if (x < -ARC / 2.0d) { + eurasia = y > -TerraUtils.ROOT3 * ARC / 2.0d; + } else { + eurasia = x * TerraUtils.ROOT3 < y; + } + + if (eurasia) { + double x0 = x; + double y0 = y; + x = SIN_THETA * y0 + COS_THETA * x0 - ARC; + y = COS_THETA * y0 - SIN_THETA * x0; + } else { + x += ARC; + } + + y += 0.75d * ARC * TerraUtils.ROOT3; + + //check to make sure still in right part + if (eurasia != isEurasianPart(x, y)) { + //the point is invalid, write NaN values into the destination array + // we assume that the exception will be thrown later when calling super.transform() when it detects NaN values + x = y = Double.NaN; + } + + dstPts[dstOff + 0] = x; + dstPts[dstOff + 1] = y; + } + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/ConformalDynmaxionProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/ConformalDynmaxionProjection.java index d052bab5..b3c37ae8 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/ConformalDynmaxionProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/ConformalDynmaxionProjection.java @@ -4,14 +4,30 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import net.buildtheearth.terraplusplus.util.MathUtils; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.buildtheearth.terraplusplus.projection.sis.AbstractOperationMethod; +import net.buildtheearth.terraplusplus.projection.sis.transform.AbstractFromGeoMathTransform2D; +import net.buildtheearth.terraplusplus.util.math.matrix.Matrix2x3; +import net.buildtheearth.terraplusplus.util.math.matrix.Matrix3x2; +import net.buildtheearth.terraplusplus.util.math.matrix.TMatrices; import net.daporkchop.lib.binary.oio.StreamUtil; import net.daporkchop.lib.common.function.io.IOSupplier; -import net.daporkchop.lib.common.ref.Ref; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; import net.daporkchop.lib.common.util.PArrays; - +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.opengis.parameter.InvalidParameterNameException; +import org.opengis.parameter.InvalidParameterValueException; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.parameter.ParameterValueGroup; + +import javax.vecmath.Vector2d; +import javax.vecmath.Vector3d; import java.io.InputStream; +import static net.buildtheearth.terraplusplus.util.TerraUtils.*; + /** * Implementation of the Dynmaxion like conformal projection. * Slightly modifies the Dynmaxion projection to make it (almost) conformal. @@ -23,9 +39,8 @@ public class ConformalDynmaxionProjection extends DymaxionProjection { protected static final double VECTOR_SCALE_FACTOR = 1.0d / 1.1473979730192934d; protected static final int SIDE_LENGTH = 256; - protected static final Ref INVERSE_CACHE = Ref.soft((IOSupplier) () -> { - double[][] vx = PArrays.filled(SIDE_LENGTH + 1, double[][]::new, i -> new double[SIDE_LENGTH + 1 - i]); - double[][] vy = PArrays.filled(SIDE_LENGTH + 1, double[][]::new, i -> new double[SIDE_LENGTH + 1 - i]); + protected static final Cached INVERSE_CACHE = Cached.global((IOSupplier) () -> { + Vector2d[][] vecs = PArrays.filledBy(SIDE_LENGTH + 1, Vector2d[][]::new, i -> new Vector2d[SIDE_LENGTH + 1 - i]); ByteBuf buf; try (InputStream in = new LzmaInputStream(ConformalDynmaxionProjection.class.getResourceAsStream("conformal.lzma"))) { @@ -34,28 +49,18 @@ public class ConformalDynmaxionProjection extends DymaxionProjection { for (int v = 0; v < SIDE_LENGTH + 1; v++) { for (int u = 0; u < SIDE_LENGTH + 1 - v; u++) { - vx[u][v] = buf.readDouble() * VECTOR_SCALE_FACTOR; - vy[u][v] = buf.readDouble() * VECTOR_SCALE_FACTOR; + vecs[u][v] = new Vector2d(buf.readDouble() * VECTOR_SCALE_FACTOR, buf.readDouble() * VECTOR_SCALE_FACTOR); } } - return new InvertableVectorField(vx, vy); - }); + return new InvertableVectorField(vecs); + }, ReferenceStrength.SOFT); protected final InvertableVectorField inverse = INVERSE_CACHE.get(); @Override - protected double[] triangleTransform(double[] vec) { - double[] c = super.triangleTransform(vec); - - double x = c[0]; - double y = c[1]; - - c[0] /= ARC; - c[1] /= ARC; - - c[0] += 0.5; - c[1] += MathUtils.ROOT3 / 6; + protected void triangleTransform(double x, double y, double z, Vector2d dst) { + triangleTransformDymaxion(x, y, z, dst); //use another interpolated vector to have a really good guess before using Newton's method //Note: foward was removed for now, will need to be added back if this improvement is ever re-implemented @@ -63,33 +68,26 @@ protected double[] triangleTransform(double[] vec) { //c = inverse.applyNewtonsMethod(x, y, c[0]/ARC + 0.5, c[1]/ARC + ROOT3/6, 1); //just use newtons method: slower - c = this.inverse.applyNewtonsMethod(x, y, c[0], c[1], 5);//c[0]/ARC + 0.5, c[1]/ARC + ROOT3/6 - - c[0] -= 0.5; - c[1] -= MathUtils.ROOT3 / 6; + this.inverse.applyNewtonsMethod(dst.x, dst.y, 5, dst, null); - c[0] *= ARC; - c[1] *= ARC; + dst.x -= 0.5d; + dst.y -= ROOT3 / 6.0d; - return c; + dst.x *= ARC; + dst.y *= ARC; } @Override - protected double[] inverseTriangleTransform(double x, double y) { - + protected void inverseTriangleTransform(double x, double y, Vector3d dst) { x /= ARC; y /= ARC; x += 0.5; - y += MathUtils.ROOT3 / 6; + y += ROOT3 / 6; - double[] c = this.inverse.getInterpolatedVector(x, y); - return super.inverseTriangleTransform(c[0], c[1]); - } - - @Override - public double metersPerUnit() { - return (40075017.0d / (2.0d * Math.PI)) / VECTOR_SCALE_FACTOR; + InvertableVectorField.Result result = InvertableVectorField.RESULT_CACHE.get(); + this.inverse.getInterpolatedVector(x, y, result); + super.inverseTriangleTransform(result.f, result.g, dst); } @Override @@ -97,22 +95,19 @@ public String toString() { return "Conformal Dymaxion"; } - private static class InvertableVectorField { - private final double[][] vx; - private final double[][] vy; + @RequiredArgsConstructor + protected static final class InvertableVectorField { + private static final Cached RESULT_CACHE = Cached.threadLocal(Result::new); - public InvertableVectorField(double[][] vx, double[][] vy) { - this.vx = vx; - this.vy = vy; - } + private final Vector2d[][] vecs; - public double[] getInterpolatedVector(double x, double y) { + public void getInterpolatedVector(double x, double y, Result dst) { //scale up triangle to be triangleSize across x *= SIDE_LENGTH; y *= SIDE_LENGTH; //convert to triangle units - double v = 2 * y / MathUtils.ROOT3; + double v = 2 / ROOT3 * y; double u = x - v * 0.5; int u1 = (int) u; @@ -139,61 +134,201 @@ public double[] getInterpolatedVector(double x, double y) { double y3; double x3; - double flip = 1; + double flip; + + if (y < -ROOT3 * (x - u1 - v1 - 1) || v1 == SIDE_LENGTH - u1 - 1) { + Vector2d vec1 = this.vecs[u1][v1]; + Vector2d vec2 = this.vecs[u1][v1 + 1]; + Vector2d vec3 = this.vecs[u1 + 1][v1]; - if (y < -MathUtils.ROOT3 * (x - u1 - v1 - 1) || v1 == SIDE_LENGTH - u1 - 1) { - valx1 = this.vx[u1][v1]; - valy1 = this.vy[u1][v1]; - valx2 = this.vx[u1][v1 + 1]; - valy2 = this.vy[u1][v1 + 1]; - valx3 = this.vx[u1 + 1][v1]; - valy3 = this.vy[u1 + 1][v1]; + valx1 = vec1.x; + valy1 = vec1.y; + valx2 = vec2.x; + valy2 = vec2.y; + valx3 = vec3.x; + valy3 = vec3.y; - y3 = 0.5 * MathUtils.ROOT3 * v1; + flip = 1; + + y3 = 0.5 * ROOT3 * v1; x3 = (u1 + 1) + 0.5 * v1; } else { - valx1 = this.vx[u1][v1 + 1]; - valy1 = this.vy[u1][v1 + 1]; - valx2 = this.vx[u1 + 1][v1]; - valy2 = this.vy[u1 + 1][v1]; - valx3 = this.vx[u1 + 1][v1 + 1]; - valy3 = this.vy[u1 + 1][v1 + 1]; + Vector2d vec1 = this.vecs[u1][v1 + 1]; + Vector2d vec2 = this.vecs[u1 + 1][v1]; + Vector2d vec3 = this.vecs[u1 + 1][v1 + 1]; + + valx1 = vec1.x; + valy1 = vec1.y; + valx2 = vec2.x; + valy2 = vec2.y; + valx3 = vec3.x; + valy3 = vec3.y; flip = -1; y = -y; - y3 = -(0.5 * MathUtils.ROOT3 * (v1 + 1)); + y3 = -(0.5 * ROOT3 * (v1 + 1)); x3 = (u1 + 1) + 0.5 * (v1 + 1); } //TODO: not sure if weights are right (but weirdly mirrors stuff so there may be simplifcation yet) - double w1 = -(y - y3) / MathUtils.ROOT3 - (x - x3); - double w2 = 2 * (y - y3) / MathUtils.ROOT3; + double w1 = -(y - y3) / ROOT3 - (x - x3); + double w2 = 2 / ROOT3 * (y - y3); double w3 = 1 - w1 - w2; - return new double[]{ valx1 * w1 + valx2 * w2 + valx3 * w3, valy1 * w1 + valy2 * w2 + valy3 * w3, - (valx3 - valx1) * SIDE_LENGTH, SIDE_LENGTH * flip * (2 * valx2 - valx1 - valx3) / MathUtils.ROOT3, - (valy3 - valy1) * SIDE_LENGTH, SIDE_LENGTH * flip * (2 * valy2 - valy1 - valy3) / MathUtils.ROOT3 }; + dst.f = valx1 * w1 + valx2 * w2 + valx3 * w3; + dst.g = valy1 * w1 + valy2 * w2 + valy3 * w3; + dst.dfdx = (valx3 - valx1) * SIDE_LENGTH; + dst.dfdy = SIDE_LENGTH / ROOT3 * flip * (2 * valx2 - valx1 - valx3); + dst.dgdx = (valy3 - valy1) * SIDE_LENGTH; + dst.dgdy = SIDE_LENGTH / ROOT3 * flip * (2 * valy2 - valy1 - valy3); } - public double[] applyNewtonsMethod(double expectedf, double expectedg, double xest, double yest, int iter) { + public void applyNewtonsMethod(double expectedf, double expectedg, int iter, Vector2d dst, Matrix2 derivativeDst) { + //porkman's notes from trying to reverse-engineer this: + // - we're trying to solve for (xest, yest) such that + // getInterpolatedVector((xest, yest)) - (expectedf, expectedg) = (0, 0) + + double xest = expectedf / ARC + 0.5d; + double yest = expectedg / ARC + (ROOT3 / 6.0d); + + Result result = RESULT_CACHE.get(); + for (int i = 0; i < iter; i++) { - double[] c = this.getInterpolatedVector(xest, yest); + this.getInterpolatedVector(xest, yest, result); + + double f = result.f - expectedf; + double g = result.g - expectedg; + + double determinant = 1 / (result.dfdx * result.dgdy - result.dfdy * result.dgdx); - double f = c[0] - expectedf; - double g = c[1] - expectedg; - double dfdx = c[2]; - double dfdy = c[3]; - double dgdx = c[4]; - double dgdy = c[5]; + xest -= determinant * (result.dgdy * f - result.dfdy * g); + yest -= determinant * (-result.dgdx * f + result.dfdx * g); + } - double determinant = 1 / (dfdx * dgdy - dfdy * dgdx); + dst.x = xest; + dst.y = yest; - xest -= determinant * (dgdy * f - dfdy * g); - yest -= determinant * (-dgdx * f + dfdx * g); + if (derivativeDst != null) { + derivativeDst.m00 = result.dfdx; + derivativeDst.m01 = result.dfdy; + derivativeDst.m10 = result.dgdx; + derivativeDst.m11 = result.dgdy; + TMatrices.invertFast(derivativeDst, derivativeDst); } + } + + public static final class Result { + public double f; + public double g; + public double dfdx; + public double dfdy; + public double dgdx; + public double dgdy; + } + } + + protected static final Cached TRANSFORM_RESOURCE_CACHE = Cached.threadLocal(TransformResourceCache::new); + + protected static class TransformResourceCache extends DymaxionProjection.TransformResourceCache { + public final Vector2d conformal_superTriangleTransform = new Vector2d(); + public final Matrix2 conformal_newtonDerivative = new Matrix2(); + } + + public static final class OperationMethod extends AbstractOperationMethod.ForLegacyProjection { + public OperationMethod() { + super("Conformal Dymaxion"); + } + + @Override + protected AbstractFromGeoMathTransform2D createBaseTransform(ParameterValueGroup parameters) throws InvalidParameterNameException, ParameterNotFoundException, InvalidParameterValueException { + return new FromGeo<>(parameters, new ToGeo<>(parameters, TRANSFORM_RESOURCE_CACHE), TRANSFORM_RESOURCE_CACHE); + } + } + + protected static class FromGeo extends DymaxionProjection.FromGeo { + private final InvertableVectorField field = INVERSE_CACHE.get(); + + public FromGeo(@NonNull ParameterValueGroup parameters, @NonNull ToGeo toGeo, @NonNull Cached cacheCache) { + super(parameters, toGeo, cacheCache); + } + + @Override + protected void triangleTransform(Vector3d rotated, Vector2d dst) { + super.triangleTransform(rotated, dst); + + //use another interpolated vector to have a really good guess before using Newton's method + //Note: foward was removed for now, will need to be added back if this improvement is ever re-implemented + //c = forward.getInterpolatedVector(c[0], c[1]); + //c = inverse.applyNewtonsMethod(x, y, c[0]/ARC + 0.5, c[1]/ARC + ROOT3/6, 1); + + //just use newtons method: slower + this.field.applyNewtonsMethod(dst.x, dst.y, 5, dst, null); + + dst.x -= 0.5d; + dst.y -= ROOT3 / 6.0d; + + dst.x *= ARC; + dst.y *= ARC; + } + + @Override + protected void triangleTransformDerivative(CACHE cache, Vector3d rotated, Matrix2x3 dst) { + Vector2d superTransform = cache.conformal_superTriangleTransform; + Matrix2 newtonDerivative = cache.conformal_newtonDerivative; + + super.triangleTransformDerivative(cache, rotated, dst); + super.triangleTransform(rotated, superTransform); + + this.field.applyNewtonsMethod(superTransform.x, superTransform.y, 5, superTransform, newtonDerivative); + TMatrices.scaleFast(newtonDerivative, ARC, newtonDerivative); + + TMatrices.multiplyFast(newtonDerivative, dst, dst); + } + } + + protected static class ToGeo extends DymaxionProjection.ToGeo { + private final InvertableVectorField field = INVERSE_CACHE.get(); + + public ToGeo(@NonNull ParameterValueGroup parameters, @NonNull Cached cacheCache) { + super(parameters, cacheCache); + } + + @Override + protected void inverseTriangleTransform(double x, double y, Vector3d dst) { + x /= ARC; + y /= ARC; + + x += 0.5d; + y += ROOT3 / 6.0d; + + InvertableVectorField.Result result = InvertableVectorField.RESULT_CACHE.get(); + this.field.getInterpolatedVector(x, y, result); + super.inverseTriangleTransform(result.f, result.g, dst); + } + + @Override + protected void inverseTriangleTransformDerivative(CACHE cache, double x, double y, Matrix3x2 dst) { + Matrix2 newtonDerivative = cache.conformal_newtonDerivative; + + x /= ARC; + y /= ARC; + + x += 0.5d; + y += ROOT3 / 6.0d; + + InvertableVectorField.Result result = InvertableVectorField.RESULT_CACHE.get(); + this.field.getInterpolatedVector(x, y, result); + + newtonDerivative.m00 = result.dfdx; + newtonDerivative.m01 = result.dfdy; + newtonDerivative.m10 = result.dgdx; + newtonDerivative.m11 = result.dgdy; + TMatrices.scaleFast(newtonDerivative, 1.0d / ARC, newtonDerivative); + + super.inverseTriangleTransformDerivative(cache, result.f, result.g, dst); - return new double[]{ xest, yest }; + TMatrices.multiplyFast(dst, newtonDerivative, dst); } } -} \ No newline at end of file +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/DymaxionProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/DymaxionProjection.java index ab6eb239..42015ab0 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/DymaxionProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/dymaxion/DymaxionProjection.java @@ -1,9 +1,34 @@ package net.buildtheearth.terraplusplus.projection.dymaxion; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import lombok.NonNull; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.MathUtils; +import net.buildtheearth.terraplusplus.projection.sis.AbstractOperationMethod; +import net.buildtheearth.terraplusplus.projection.sis.AbstractSISMigratedGeographicProjection; +import net.buildtheearth.terraplusplus.projection.sis.transform.AbstractFromGeoMathTransform2D; +import net.buildtheearth.terraplusplus.projection.sis.transform.AbstractToGeoMathTransform2D; +import net.buildtheearth.terraplusplus.util.TerraUtils; +import net.buildtheearth.terraplusplus.util.math.matrix.Matrix2x3; +import net.buildtheearth.terraplusplus.util.math.matrix.Matrix3x2; +import net.buildtheearth.terraplusplus.util.math.matrix.TMatrices; +import net.daporkchop.lib.common.reference.cache.Cached; +import org.apache.sis.internal.util.DoubleDouble; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.Matrix3; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.transform.ContextualParameters; +import org.apache.sis.referencing.operation.transform.IterationStrategy; +import org.opengis.parameter.InvalidParameterNameException; +import org.opengis.parameter.InvalidParameterValueException; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.TransformException; + +import javax.vecmath.Vector2d; +import javax.vecmath.Vector3d; +import java.util.Arrays; + +import static net.buildtheearth.terraplusplus.util.TerraUtils.*; /** * Implementation of the Dynmaxion projection. @@ -12,7 +37,7 @@ * @see Wikipedia's article on the Dynmaxion projection */ @JsonDeserialize -public class DymaxionProjection implements GeographicProjection { +public class DymaxionProjection extends AbstractSISMigratedGeographicProjection { protected static final double ARC = 2 * Math.asin(Math.sqrt(5 - Math.sqrt(5)) / Math.sqrt(10)); protected static final double Z = Math.sqrt(5 + 2 * Math.sqrt(5)) / Math.sqrt(15); @@ -34,7 +59,7 @@ public class DymaxionProjection implements GeographicProjection { * * @see Wikipedia */ - protected static final double[][] VERTICES = { + private static final double[][] VERTICES = { { 10.536199, 64.700000 }, { -5.245390, 2.300882 }, { 58.157706, 10.447378 }, @@ -53,7 +78,7 @@ public class DymaxionProjection implements GeographicProjection { * Indicates the vertices forming each face of the icosahedron. * Each entry refers to the index of a vertex in {@link #VERTICES} */ - protected static final int[][] ISO = { + private static final int[][] ISO = { { 2, 1, 6 }, { 1, 0, 2 }, { 0, 1, 5 }, @@ -78,58 +103,65 @@ public class DymaxionProjection implements GeographicProjection { { 3, 7, 8 } //child of 15 }; - protected static final double[][] CENTER_MAP = { - { -3, 7 }, - { -2, 5 }, - { -1, 7 }, - { 2, 5 }, - { 4, 5 }, - { -4, 1 }, - { -3, -1 }, - { -2, 1 }, - { -1, -1 }, - { 0, 1 }, - { 1, -1 }, - { 2, 1 }, - { 3, -1 }, - { 4, 1 }, - { 5, -1 }, //14, left side, right to be cut - { -3, -5 }, - { -1, -5 }, - { 1, -5 }, - { 2, -7 }, - { -4, -7 }, - { -5, -5 }, //20, pseudo triangle, child of 14 - { -2, -7 } //21 , pseudo triangle, child of 15 + private static final Vector2d[] CENTER_MAP = { + new Vector2d(-3, 7), + new Vector2d(-2, 5), + new Vector2d(-1, 7), + new Vector2d(2, 5), + new Vector2d(4, 5), + new Vector2d(-4, 1), + new Vector2d(-3, -1), + new Vector2d(-2, 1), + new Vector2d(-1, -1), + new Vector2d(0, 1), + new Vector2d(1, -1), + new Vector2d(2, 1), + new Vector2d(3, -1), + new Vector2d(4, 1), + new Vector2d(5, -1), //14, left side, right to be cut + new Vector2d(-3, -5), + new Vector2d(-1, -5), + new Vector2d(1, -5), + new Vector2d(2, -7), + new Vector2d(-4, -7), + new Vector2d(-5, -5), //20, pseudo triangle, child of 14 + new Vector2d(-2, -7), //21 , pseudo triangle, child of 15 }; /** * Indicates for each face if it needs to be flipped after projecting */ - protected static final boolean[] FLIP_TRIANGLE = { + private static final boolean[] FLIP_TRIANGLE = { true, false, true, false, false, true, false, true, false, true, false, true, false, true, false, true, true, true, false, false, true, false }; + /** + * Indicates for each face if it needs to be flipped after projecting. + *

+ * Each element is {@code -1.0d} if the corresponding element in {@link #FLIP_TRIANGLE} is {@code true}, and {@code 1.0d} otherwise. + */ + private static final double[] FLIP_TRIANGLE_FACTOR; + /** * This contains the Cartesian coordinates the centroid * of each face of the icosahedron. */ - protected static final double[][] CENTROIDS = new double[22][3]; + protected static final Vector3d[] CENTROIDS = new Vector3d[22]; /** * Rotation matrices to move the triangles to the reference coordinates from the original positions. * Indexed by the face's indices. */ - protected static final double[][][] ROTATION_MATRICES = new double[22][3][3]; + protected static final Matrix3[] ROTATION_MATRICES = new Matrix3[22]; /** * Rotation matrices to move the triangles from the reference coordinates to their original positions. * Indexed by the face's indices. */ - protected static final double[][][] INVERSE_ROTATION_MATRICES = new double[22][3][3]; + protected static final Matrix3[] INVERSE_ROTATION_MATRICES = new Matrix3[22]; protected static final int[] FACE_ON_GRID = { -1, -1, 0, 1, 2, -1, -1, 3, -1, 4, -1, @@ -138,10 +170,9 @@ public class DymaxionProjection implements GeographicProjection { }; static { - for (int i = 0; i < 22; i++) { - CENTER_MAP[i][0] *= 0.5 * ARC; - CENTER_MAP[i][1] *= ARC * MathUtils.ROOT3 / 12; + CENTER_MAP[i].x *= 0.5d * ARC; + CENTER_MAP[i].y *= ARC * ROOT3 / 12.0d; } // Will contain the list of vertices in Cartesian coordinates @@ -149,14 +180,13 @@ public class DymaxionProjection implements GeographicProjection { // Convert the geographic vertices to spherical in radians for (int i = 0; i < VERTICES.length; i++) { - double[] vertexSpherical = MathUtils.geo2Spherical(VERTICES[i]); - double[] vertex = MathUtils.spherical2Cartesian(vertexSpherical); + double[] vertexSpherical = TerraUtils.geo2Spherical(VERTICES[i]); + double[] vertex = TerraUtils.spherical2Cartesian(vertexSpherical); verticesCartesian[i] = vertex; VERTICES[i] = vertexSpherical; } for (int i = 0; i < 22; i++) { - // Vertices of the current face double[] vec1 = verticesCartesian[ISO[i][0]]; double[] vec2 = verticesCartesian[ISO[i][1]]; @@ -167,19 +197,23 @@ public class DymaxionProjection implements GeographicProjection { double ysum = vec1[1] + vec2[1] + vec3[1]; double zsum = vec1[2] + vec2[2] + vec3[2]; double mag = Math.sqrt(xsum * xsum + ysum * ysum + zsum * zsum); - CENTROIDS[i] = new double[]{ xsum / mag, ysum / mag, zsum / mag }; + CENTROIDS[i] = new Vector3d(xsum / mag, ysum / mag, zsum / mag); - double[] centroidSpherical = MathUtils.cartesian2Spherical(CENTROIDS[i]); - double centroidLambda = centroidSpherical[0]; - double centroidPhi = centroidSpherical[1]; + Vector2d centroidSpherical = TerraUtils.cartesian2Spherical(CENTROIDS[i]); + double centroidLambda = centroidSpherical.x; + double centroidPhi = centroidSpherical.y; double[] vertex = VERTICES[ISO[i][0]]; double[] v = { vertex[0] - centroidLambda, vertex[1] }; v = yRot(v, -centroidPhi); - ROTATION_MATRICES[i] = MathUtils.produceZYZRotationMatrix(-centroidLambda, -centroidPhi, (Math.PI / 2) - v[0]); - INVERSE_ROTATION_MATRICES[i] = MathUtils.produceZYZRotationMatrix(v[0] - (Math.PI / 2), centroidPhi, centroidLambda); + ROTATION_MATRICES[i] = TerraUtils.produceZYZRotationMatrix(-centroidLambda, -centroidPhi, (Math.PI / 2) - v[0]); + INVERSE_ROTATION_MATRICES[i] = TerraUtils.produceZYZRotationMatrix(v[0] - (Math.PI / 2), centroidPhi, centroidLambda); + } + FLIP_TRIANGLE_FACTOR = new double[FLIP_TRIANGLE.length]; + for (int i = 0; i < FLIP_TRIANGLE.length; i++) { + FLIP_TRIANGLE_FACTOR[i] = FLIP_TRIANGLE[i] ? -1.0d : 1.0d; } } @@ -187,7 +221,7 @@ protected static int findTriangleGrid(double x, double y) { //cast equilateral triangles to 45 degrees right triangles (side length of root2) double xp = x / ARC; - double yp = y / (ARC * MathUtils.ROOT3); + double yp = y / (ARC * TerraUtils.ROOT3); int row; if (yp > -0.25) { @@ -227,7 +261,7 @@ protected static int findTriangleGrid(double x, double y) { } protected static double[] yRot(double[] spherical, double rot) { - double[] c = MathUtils.spherical2Cartesian(spherical); + double[] c = TerraUtils.spherical2Cartesian(spherical); double x = c[0]; c[0] = c[2] * Math.sin(rot) + x * Math.cos(rot); @@ -248,22 +282,22 @@ protected static double[] yRot(double[] spherical, double rot) { * Finds the face of the icosahedron on which to project a point. * In practice, it works by finding the face with the closest centroid to the point. * - * @param vector - position vector as double array of length 3, using Cartesian coordinates + * @param x - the X coordinate, in Cartesian coordinates + * @param y - the Y coordinate, in Cartesian coordinates + * @param z - the Z coordinate, in Cartesian coordinates * @return an integer identifying the face on which to project the point */ - protected int findTriangle(double[] vector) { - + protected static int findTriangle(double x, double y, double z) { double min = Double.MAX_VALUE; int face = 0; for (int i = 0; i < 20; i++) { - double xd = CENTROIDS[i][0] - vector[0]; - double yd = CENTROIDS[i][1] - vector[1]; - double zd = CENTROIDS[i][2] - vector[2]; + double xd = CENTROIDS[i].x - x; + double yd = CENTROIDS[i].y - y; + double zd = CENTROIDS[i].z - z; double dissq = xd * xd + yd * yd + zd * zd; if (dissq < min) { - if (dissq < 0.1) //TODO: enlarge radius { return i; @@ -277,24 +311,185 @@ protected int findTriangle(double[] vector) { return face; } - protected double[] triangleTransform(double[] vec) { + protected static int findTriangle(Vector3d cartesian) { + return findTriangle(cartesian.x, cartesian.y, cartesian.z); + } - double S = Z / vec[2]; + protected static void triangleTransformDymaxion(double x, double y, double z, Vector2d dst) { + double S = Z / z; - double xp = S * vec[0]; - double yp = S * vec[1]; + double xp = S * x; + double yp = S * y; - double a = Math.atan((2 * yp / MathUtils.ROOT3 - EL6) / DVE); //ARC/2 terms cancel - double b = Math.atan((xp - yp / MathUtils.ROOT3 - EL6) / DVE); - double c = Math.atan((-xp - yp / MathUtils.ROOT3 - EL6) / DVE); + double a = Math.atan((2 * yp / TerraUtils.ROOT3 - EL6) / DVE); //ARC/2 terms cancel + double b = Math.atan((xp - yp / TerraUtils.ROOT3 - EL6) / DVE); + double c = Math.atan((-xp - yp / TerraUtils.ROOT3 - EL6) / DVE); - return new double[]{ 0.5 * (b - c), (2 * a - b - c) / (2 * MathUtils.ROOT3) }; + dst.x = 0.5 * (b - c); + dst.y = (2 * a - b - c) / (2 * TerraUtils.ROOT3); } - protected double[] inverseTriangleTransformNewton(double xpp, double ypp) { + protected static void triangleTransformDymaxion(Vector3d rotated, Vector2d dst) { + triangleTransformDymaxion(rotated.x, rotated.y, rotated.z, dst); + } + + protected static void triangleTransformDymaxionDerivative(double x, double y, double z, Matrix2x3 dst) { + //double S = Z / z; // (Z / z) + //double xp = S * x; // (Z / z * x) + //double yp = S * y; // (Z / z * y) + + //double a = Math.atan((2 * yp / TerraUtils.ROOT3 - EL6) / DVE); // atan((2 * (Z / z * y) / sqrt(3) - L) / D) + //double b = Math.atan((xp - yp / TerraUtils.ROOT3 - EL6) / DVE); // atan(((Z / z * x) - (Z / z * y) / sqrt(3) - L) / D) + //double c = Math.atan((-xp - yp / TerraUtils.ROOT3 - EL6) / DVE); // atan((-(Z / z * x) - (Z / z * y) / sqrt(3) - L) / D) + + /*return new double[] { + // (1 / 2) * (atan(((Z / z * x) - (Z / z * y) / sqrt(3) - L) / D) - atan((-(Z / z * x) - (Z / z * y) / sqrt(3) - L) / D)) + // (1 / 2) * (ArcTan(((Z / z * x) - (Z / z * y) / sqrt(3) - L) / D) - ArcTan((-(Z / z * x) - (Z / z * y) / sqrt(3) - L) / D)) + 0.5 * (b - c), + // (2 * atan((2 * (Z / z * y) / sqrt(3) - L) / D) - atan(((Z / z * x) - (Z / z * y) / sqrt(3) - L) / D) - atan((-(Z / z * x) - (Z / z * y) / sqrt(3) - L) / D)) / (2 * sqrt(3)) + // (2 * ArcTan((2 * (Z / z * y) / sqrt(3) - L) / D) - ArcTan(((Z / z * x) - (Z / z * y) / sqrt(3) - L) / D) - ArcTan((-(Z / z * x) - (Z / z * y) / sqrt(3) - L) / D)) / (2 * sqrt(3)) + (2 * a - b - c) / (2 * TerraUtils.ROOT3) + };*/ + + // Times[Rational[1,2],Plus[Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1]],Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1]]]] + // Times[Rational[1,2],Plus[Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]],Times[-1,Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]]]] + // Times[Rational[1,2],Plus[Times[Power[D,-1],Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Plus[Times[-1,x,Power[z,-2],Z],Times[y,Power[z,-2],Z,Power[sqrt[3],-1]]]],Times[-1,Power[D,-1],Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Plus[Times[x,Power[z,-2],Z],Times[y,Power[z,-2],Z,Power[sqrt[3],-1]]]]]] + // Times[Rational[1,2],Plus[Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1]],Times[-1,Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1]]],Power[sqrt[3],-1]] + // Times[Rational[1,2],Plus[Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]],Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]],Times[4,Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[2,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]]],Power[sqrt[3],-1]] + // Times[Rational[1,2],Plus[Times[-1,Power[D,-1],Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Plus[Times[-1,x,Power[z,-2],Z],Times[y,Power[z,-2],Z,Power[sqrt[3],-1]]]],Times[-1,Power[D,-1],Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Plus[Times[x,Power[z,-2],Z],Times[y,Power[z,-2],Z,Power[sqrt[3],-1]]]],Times[-4,Power[D,-1],y,Power[z,-2],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[2,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]]],Power[sqrt[3],-1]] + + //System.out.println(mathematicaFullFormToJava("Times[Rational[1,2],Plus[Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1]],Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1]]]]")); + //System.out.println(mathematicaFullFormToJava("Times[Rational[1,2],Plus[Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]],Times[-1,Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]]]]")); + //System.out.println(mathematicaFullFormToJava("Times[Rational[1,2],Plus[Times[Power[D,-1],Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Plus[Times[-1,x,Power[z,-2],Z],Times[y,Power[z,-2],Z,Power[sqrt[3],-1]]]],Times[-1,Power[D,-1],Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Plus[Times[x,Power[z,-2],Z],Times[y,Power[z,-2],Z,Power[sqrt[3],-1]]]]]]")); + //System.out.println(mathematicaFullFormToJava("Times[Rational[1,2],Plus[Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1]],Times[-1,Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1]]],Power[sqrt[3],-1]]")); + //System.out.println(mathematicaFullFormToJava("Times[Rational[1,2],Plus[Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]],Times[Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]],Times[4,Power[D,-1],Power[z,-1],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[2,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]]],Power[sqrt[3],-1]]")); + //System.out.println(mathematicaFullFormToJava("Times[Rational[1,2],Plus[Times[-1,Power[D,-1],Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Plus[Times[-1,x,Power[z,-2],Z],Times[y,Power[z,-2],Z,Power[sqrt[3],-1]]]],Times[-1,Power[D,-1],Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[-1,x,Power[z,-1],Z],Times[-1,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Plus[Times[x,Power[z,-2],Z],Times[y,Power[z,-2],Z,Power[sqrt[3],-1]]]],Times[-4,Power[D,-1],y,Power[z,-2],Z,Power[Plus[1,Times[Power[D,-2],Power[Plus[Times[-1,L],Times[2,y,Power[z,-1],Z,Power[sqrt[3],-1]]],2]]],-1],Power[sqrt[3],-1]]],Power[sqrt[3],-1]]")); + //System.exit(0); + + double d0 = sq(-EL6 - x / z * Z - Z / ROOT3 * y / z); + double d1 = sq(-EL6 + x / z * Z - Z / ROOT3 * y / z); + double d2 = sq(-EL6 + 2.0d * Z / ROOT3 * y / z); + + double v0 = z + z * d0 / (DVE * DVE); + double v1 = z + z * d1 / (DVE * DVE); + double v2 = Z / DVE / v0; + double v3 = Z / DVE / v1; + + double v4 = Z / ROOT3 * y / sq(z); + double v6 = 1.0d / DVE / (1.0d + d1 / (DVE * DVE)) * (-Z * x / sq(z) + v4); + double v7 = 1.0d / DVE / (1.0d + d0 / (DVE * DVE)) * (Z * x / sq(z) + v4); + + dst.m00 = 0.5d * (v2 + v3); + dst.m01 = 0.5d * (Z / DVE / ROOT3 / v0 - Z / DVE / ROOT3 / v1); + + dst.m02 = 0.5d * (v6 - v7); + dst.m10 = 0.5d / ROOT3 * (v2 - v3); + dst.m11 = 0.5d / ROOT3 * (Z / DVE / ROOT3 / v0 + Z / DVE / ROOT3 / v1 + 4.0d / DVE * Z / ROOT3 / z / (1.0d + 1.0d / (DVE * DVE) * d2)); + dst.m12 = 0.5d / ROOT3 * (-v6 - v7 + -4.0d / DVE * Z / ROOT3 * y / (sq(z) + sq(z) / (DVE * DVE) * d2)); + } + + protected static void triangleTransformDymaxionDerivative(Vector3d rotated, Matrix2x3 dst) { + triangleTransformDymaxionDerivative(rotated.x, rotated.y, rotated.z, dst); + } + + /*public static String mathematicaFullFormToJava(String fullForm) { + fullForm = fullForm.trim(); + + try { + return String.format("%.1fd", Double.parseDouble(fullForm)); + } catch (NumberFormatException e) { + // not a numeric literal + } + + switch (fullForm) { + case "x": + case "y": + case "z": + case "Z": + return fullForm; + case "D": + return "DVE"; + case "L": + return "EL6"; + } + + int i = fullForm.indexOf('['); + String func = fullForm.substring(0, i); + + List operands = new ArrayList<>(); + + LOOP: + for (int depth = 0, lastOperandStart = ++i; ; i++) { + switch (fullForm.charAt(i)) { + case '[': + depth++; + break; + case ']': + if (depth-- == 0) { + operands.add(fullForm.substring(lastOperandStart, i)); + break LOOP; + } + break; + case ',': + if (depth == 0) { + operands.add(fullForm.substring(lastOperandStart, i)); + lastOperandStart = i + 1; + } + break; + } + } + + switch (func) { + case "Plus": + return operands.stream().map(DymaxionProjection::mathematicaFullFormToJava).collect(Collectors.joining(" + ", "(", ")")); + case "Times": { + String prefix = "("; + if ("-1".equals(operands.get(0))) { + operands.remove(0); + prefix = "-("; + } + return operands.stream().map(DymaxionProjection::mathematicaFullFormToJava).collect(Collectors.joining(" * ", prefix, ")")); + } + case "Rational": + assert operands.size() == 2 : fullForm; + return '(' + mathematicaFullFormToJava(operands.get(0)) + " / " + mathematicaFullFormToJava(operands.get(1)) + ')'; + case "Power": + assert operands.size() == 2 : fullForm; + try { + int pow = Integer.parseInt(operands.get(1)); + String prefix = ""; + String suffix = ""; + if (pow < 0) { + pow = -pow; + prefix = "1.0d / ("; + suffix = ")"; + } + + switch (pow) { + case 1: + return prefix + mathematicaFullFormToJava(operands.get(0)) + suffix; + case 2: + return prefix + "sq(" + mathematicaFullFormToJava(operands.get(0)) + ')' + suffix; + } + } catch (NumberFormatException e) { + // ignore + } + return "Math.pow(" + mathematicaFullFormToJava(operands.get(0)) + ", " + mathematicaFullFormToJava(operands.get(1)) + ')'; + case "sqrt": + assert operands.size() == 1 : fullForm; + return "ROOT" + operands.get(0); + } + + throw new IllegalArgumentException(fullForm); + }*/ + protected void triangleTransform(double x, double y, double z, Vector2d dst) { + triangleTransformDymaxion(x, y, z, dst); + } + + protected static void inverseTriangleTransformNewton(double xpp, double ypp, Vector3d dst) { //a & b are linearly related to c, so using the tan of sum formula we know: tan(c+off) = (tanc + tanoff)/(1-tanc*tanoff) - double tanaoff = Math.tan(MathUtils.ROOT3 * ypp + xpp); // a = c + root3*y'' + x'' + double tanaoff = Math.tan(TerraUtils.ROOT3 * ypp + xpp); // a = c + root3*y'' + x'' double tanboff = Math.tan(2 * xpp); // b = c + 2x'' double anumer = tanaoff * tanaoff + 1; @@ -327,8 +522,8 @@ protected double[] inverseTriangleTransformNewton(double xpp, double ypp) { } //simple reversal algebra based on tan values - double yp = MathUtils.ROOT3 * (DVE * tana + EL6) / 2; - double xp = DVE * tanb + yp / MathUtils.ROOT3 + EL6; + double yp = TerraUtils.ROOT3 * (DVE * tana + EL6) / 2; + double xp = DVE * tanb + yp / TerraUtils.ROOT3 + EL6; //x = z*xp/Z, y = z*yp/Z, x^2 + y^2 + z^2 = 1 double xpoZ = xp / Z; @@ -336,44 +531,61 @@ protected double[] inverseTriangleTransformNewton(double xpp, double ypp) { double z = 1 / Math.sqrt(1 + xpoZ * xpoZ + ypoZ * ypoZ); - return new double[]{ z * xpoZ, z * ypoZ, z }; + dst.x = z * xpoZ; + dst.y = z * ypoZ; + dst.z = z; } - protected double[] inverseTriangleTransform(double x, double y) { - return this.inverseTriangleTransformNewton(x, y); + protected static void inverseTriangleTransformNewtonDerivative(TransformResourceCache cache, double xpp, double ypp, Matrix3x2 dst) { + Vector3d vec = cache.inverseTriangleTransformNewton_temporaryVector; + Matrix2x3 matrix = cache.inverseTriangleTransformNewton_temporaryMatrix; + + inverseTriangleTransformNewton(xpp, ypp, vec); + triangleTransformDymaxionDerivative(vec.x, vec.y, vec.z, matrix); + TMatrices.pseudoInvertFast(matrix, dst); + } + + protected void inverseTriangleTransform(double x, double y, Vector3d dst) { + inverseTriangleTransformNewton(x, y, dst); } @Override public double[] fromGeo(double longitude, double latitude) throws OutOfProjectionBoundsException { - - OutOfProjectionBoundsException.checkLongitudeLatitudeInRange(longitude, latitude); + OutOfProjectionBoundsException.checkLongitudeLatitudeInRange(longitude, latitude); + + TransformResourceCache cache = TRANSFORM_RESOURCE_CACHE.get(); - double[] vector = MathUtils.spherical2Cartesian(MathUtils.geo2Spherical(new double[]{ longitude, latitude })); + Vector2d spherical = cache.spherical; + Vector3d cartesian = cache.cartesian; + Vector3d rotated = cache.rotated; + Vector2d projected = cache.projected; - int face = this.findTriangle(vector); + TerraUtils.geo2Spherical(longitude, latitude, spherical); + TerraUtils.spherical2Cartesian(spherical.x, spherical.y, cartesian); + + int face = findTriangle(cartesian); //apply rotation matrix (move triangle onto template triangle) - double[] pvec = MathUtils.matVecProdD(ROTATION_MATRICES[face], vector); - double[] projectedVec = this.triangleTransform(pvec); + TMatrices.multiplyFast(ROTATION_MATRICES[face], cartesian, rotated); + this.triangleTransform(rotated.x, rotated.y, rotated.z, projected); //flip triangle to correct orientation - if (FLIP_TRIANGLE[face]) { - projectedVec[0] = -projectedVec[0]; - projectedVec[1] = -projectedVec[1]; - } + final double projectedX = projected.x * FLIP_TRIANGLE_FACTOR[face]; + final double projectedY = projected.y * FLIP_TRIANGLE_FACTOR[face]; - vector[0] = projectedVec[0]; + double effectiveProjectedX = projectedX; + double effectiveProjectedY = projectedY; //deal with special snowflakes (child faces 20, 21) - if (((face == 15 && vector[0] > projectedVec[1] * MathUtils.ROOT3) || face == 14) && vector[0] > 0) { - projectedVec[0] = 0.5 * vector[0] - 0.5 * MathUtils.ROOT3 * projectedVec[1]; - projectedVec[1] = 0.5 * MathUtils.ROOT3 * vector[0] + 0.5 * projectedVec[1]; + if (((face == 15 && projectedX > projectedY * TerraUtils.ROOT3) || face == 14) && projectedX > 0) { + effectiveProjectedX = 0.5 * projectedX - 0.5 * TerraUtils.ROOT3 * projectedY; + effectiveProjectedY = 0.5 * TerraUtils.ROOT3 * projectedX + 0.5 * projectedY; face += 6; //shift 14->20 & 15->21 } - projectedVec[0] += CENTER_MAP[face][0]; - projectedVec[1] += CENTER_MAP[face][1]; - - return projectedVec; + return new double[]{ + effectiveProjectedX + CENTER_MAP[face].x, + effectiveProjectedY + CENTER_MAP[face].y, + }; } @Override @@ -384,8 +596,8 @@ public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException throw OutOfProjectionBoundsException.get(); } - x -= CENTER_MAP[face][0]; - y -= CENTER_MAP[face][1]; + x -= CENTER_MAP[face].x; + y -= CENTER_MAP[face].y; //deal with bounds of special snowflakes switch (face) { @@ -395,45 +607,48 @@ public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException } break; case 20: - if (-y * MathUtils.ROOT3 > x) { + if (-y * TerraUtils.ROOT3 > x) { throw OutOfProjectionBoundsException.get(); } break; case 15: - if (x > 0 && x > y * MathUtils.ROOT3) { + if (x > 0 && x > y * TerraUtils.ROOT3) { throw OutOfProjectionBoundsException.get(); } break; case 21: - if (x < 0 || -y * MathUtils.ROOT3 > x) { + if (x < 0 || -y * TerraUtils.ROOT3 > x) { throw OutOfProjectionBoundsException.get(); } break; } //flip triangle to upright orientation (if not already) - if (FLIP_TRIANGLE[face]) { - x = -x; - y = -y; - } + x *= FLIP_TRIANGLE_FACTOR[face]; + y *= FLIP_TRIANGLE_FACTOR[face]; + + TransformResourceCache cache = TRANSFORM_RESOURCE_CACHE.get(); + + Vector2d geo = cache.geo; + Vector2d spherical = cache.spherical; + Vector3d cartesian = cache.cartesian; + Vector3d rotated = cache.rotated; //invert triangle transform - double[] c = this.inverseTriangleTransform(x, y); - x = c[0]; - y = c[1]; - double z = c[2]; + this.inverseTriangleTransform(x, y, rotated); - double[] vec = { x, y, z }; //apply inverse rotation matrix (move triangle from template triangle to correct position on globe) - double[] vecp = MathUtils.matVecProdD(INVERSE_ROTATION_MATRICES[face], vec); + TMatrices.multiplyFast(INVERSE_ROTATION_MATRICES[face], rotated, cartesian); //convert back to geo coordinates - return MathUtils.spherical2Geo(MathUtils.cartesian2Spherical(vecp)); + TerraUtils.cartesian2Spherical(cartesian.x, cartesian.y, cartesian.z, spherical); + TerraUtils.spherical2Geo(spherical.x, spherical.y, geo); + return new double[]{ geo.x, geo.y }; } @Override public double[] bounds() { - return new double[]{ -3 * ARC, -0.75 * ARC * MathUtils.ROOT3, 2.5 * ARC, 0.75 * ARC * MathUtils.ROOT3 }; + return new double[]{ -3 * ARC, -0.75 * ARC * TerraUtils.ROOT3, 2.5 * ARC, 0.75 * ARC * TerraUtils.ROOT3 }; } @Override @@ -441,13 +656,423 @@ public boolean upright() { return false; } - @Override - public double metersPerUnit() { - return Math.sqrt(510100000000000.0 / (20 * MathUtils.ROOT3 * ARC * ARC / 4)); - } - @Override public String toString() { return "Dymaxion"; } + + private static final Cached TRANSFORM_RESOURCE_CACHE = Cached.threadLocal(TransformResourceCache::new); + + protected static class TransformResourceCache { + public final Vector2d geo = new Vector2d(); + public final Vector2d spherical = new Vector2d(); + public final Vector3d cartesian = new Vector3d(); + public final Vector3d rotated = new Vector3d(); + public final Vector2d projected = new Vector2d(); + + public final Matrix3x2 cartesianDerivative = Matrix3x2.createZero(); + public final Matrix3x2 rotatedDerivative = Matrix3x2.createZero(); + public final Matrix2x3 projectedDerivative = Matrix2x3.createZero(); + public final Matrix2 totalDerivative = new Matrix2(); + + public final Vector3d inverseTriangleTransformNewton_temporaryVector = new Vector3d(); + public final Matrix2x3 inverseTriangleTransformNewton_temporaryMatrix = Matrix2x3.createZero(); + } + + public static final class OperationMethod extends AbstractOperationMethod.ForLegacyProjection { + public OperationMethod() { + super("Dymaxion"); + } + + @Override + protected AbstractFromGeoMathTransform2D createBaseTransform(ParameterValueGroup parameters) throws InvalidParameterNameException, ParameterNotFoundException, InvalidParameterValueException { + return new FromGeo<>(parameters, new ToGeo<>(parameters, TRANSFORM_RESOURCE_CACHE), TRANSFORM_RESOURCE_CACHE); + } + } + + protected static class FromGeo extends AbstractFromGeoMathTransform2D { + private static final Matrix2 SPECIAL_FACTOR = new Matrix2(0.5d, -0.5d * ROOT3, 0.5d * ROOT3, 0.5d); + + protected final Cached cacheCache; + + public FromGeo(@NonNull ParameterValueGroup parameters, @NonNull ToGeo toGeo, @NonNull Cached cacheCache) { + super(parameters, toGeo); + + this.cacheCache = cacheCache; + } + + protected void triangleTransform(Vector3d rotated, Vector2d dst) { + triangleTransformDymaxion(rotated, dst); + } + + protected void triangleTransformDerivative(CACHE cache, Vector3d rotated, Matrix2x3 dst) { + triangleTransformDymaxionDerivative(rotated, dst); + } + + @Override + protected void configureMatrices(ContextualParameters contextualParameters, MatrixSIS normalize, MatrixSIS denormalize) { + //TerraUtils.geo2Spherical() + normalize.convertAfter(0, DoubleDouble.createDegreesToRadians(), null); + normalize.convertAfter(1, -1L, 90.0d); //90 - geo[1] + normalize.convertAfter(1, DoubleDouble.createDegreesToRadians(), null); + } + + @Override + public Matrix2 transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws TransformException { + final double origLon = srcPts[srcOff + 0]; + final double origLat = srcPts[srcOff + 1]; + + if (!isLongitudeLatitudeInRange(origLon, origLat) || isInvalidCoordinates(origLon, origLat)) { //propagate NaN coordinates immediately + throw this.getSingleExceptionForSelf(); + } + + CACHE cache = this.cacheCache.get(); + + Vector3d cartesian = cache.cartesian; + Vector3d rotated = cache.rotated; + Vector2d projected = cache.projected; + + TerraUtils.spherical2Cartesian(origLon, origLat, cartesian); + final int origFace = findTriangle(cartesian); + + //apply rotation matrix (move triangle onto template triangle) + TMatrices.multiplyFast(ROTATION_MATRICES[origFace], cartesian, rotated); + this.triangleTransform(rotated, projected); + + //flip triangle to correct orientation + final double origProjectedX = projected.x * FLIP_TRIANGLE_FACTOR[origFace]; + final double origProjectedY = projected.y * FLIP_TRIANGLE_FACTOR[origFace]; + + if (dstPts != null) { + double effectiveProjectedX = origProjectedX; + double effectiveProjectedY = origProjectedY; + int effectiveOffsetFace = origFace; + + //deal with special snowflakes (child faces 20, 21) + if (((origFace == 15 && origProjectedX > ROOT3 * origProjectedY) || origFace == 14) && origProjectedX > 0) { + effectiveProjectedX = 0.5d * origProjectedX - 0.5d * ROOT3 * origProjectedY; + effectiveProjectedY = 0.5d * ROOT3 * origProjectedX + 0.5d * origProjectedY; + effectiveOffsetFace += 6; //shift 14->20 & 15->21 + } + + dstPts[dstOff + 0] = effectiveProjectedX + CENTER_MAP[effectiveOffsetFace].x; + dstPts[dstOff + 1] = effectiveProjectedY + CENTER_MAP[effectiveOffsetFace].y; + } + if (!derivate) { + return null; + } + + Matrix3x2 cartesianDerivative = cache.cartesianDerivative; + Matrix3x2 rotatedDerivative = cache.rotatedDerivative; + Matrix2x3 projectedDerivative = cache.projectedDerivative; + Matrix2 totalDerivative = cache.totalDerivative; + + TerraUtils.spherical2CartesianDerivative(origLon, origLat, cartesianDerivative); + TMatrices.multiplyFast(ROTATION_MATRICES[origFace], cartesianDerivative, rotatedDerivative); + this.triangleTransformDerivative(cache, rotated, projectedDerivative); + TMatrices.multiplyFast(projectedDerivative, rotatedDerivative, totalDerivative); + + //flip triangle to correct orientation + TMatrices.scaleFast(totalDerivative, FLIP_TRIANGLE_FACTOR[origFace], totalDerivative); + + //deal with special snowflakes (child faces 20, 21) + if (((origFace == 15 && origProjectedX > ROOT3 * origProjectedY) || origFace == 14) && origProjectedX > 0) { + return TMatrices.multiplyFast(SPECIAL_FACTOR, totalDerivative); + } else { + return totalDerivative.clone(); + } + } + + @Override + public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException { + int srcInc = 2; + int dstInc = 2; + + //noinspection ArrayEquality + if (srcPts == dstPts) { + switch (IterationStrategy.suggest(srcOff, 2, dstOff, 2, numPts)) { + case ASCENDING: { + break; + } + case DESCENDING: { + srcOff += 2 * (numPts - 1); + dstOff += 2 * (numPts - 1); + srcInc -= 4; + dstInc -= 4; + break; + } + default: { + //TODO: use array allocator for this + srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts * 2); + srcOff = 0; + break; + } + } + } + + CACHE cache = this.cacheCache.get(); + + Vector3d cartesian = cache.cartesian; + Vector3d rotated = cache.rotated; + Vector2d projected = cache.projected; + + boolean anyFailed = false; + for (; --numPts >= 0; srcOff += srcInc, dstOff += dstInc) { + final double origLon = srcPts[srcOff + 0]; + final double origLat = srcPts[srcOff + 1]; + + if (!isLongitudeLatitudeInRange(origLon, origLat) || isInvalidCoordinates(origLon, origLat)) { + //out of bounds/invalid, fill destination buffer with NaN values and keep going + fillNaN(dstPts, dstOff); + anyFailed = true; + continue; + } + + // + // the following is the same algorithm as above, but copied here for performance + // + + TerraUtils.spherical2Cartesian(origLon, origLat, cartesian); + final int origFace = findTriangle(cartesian); + + //apply rotation matrix (move triangle onto template triangle) + TMatrices.multiplyFast(ROTATION_MATRICES[origFace], cartesian, rotated); + this.triangleTransform(rotated, projected); + + //flip triangle to correct orientation + final double origProjectedX = projected.x * FLIP_TRIANGLE_FACTOR[origFace]; + final double origProjectedY = projected.y * FLIP_TRIANGLE_FACTOR[origFace]; + + double effectiveProjectedX = origProjectedX; + double effectiveProjectedY = origProjectedY; + int effectiveOffsetFace = origFace; + + //deal with special snowflakes (child faces 20, 21) + if (((origFace == 15 && origProjectedX > ROOT3 * origProjectedY) || origFace == 14) && origProjectedX > 0) { + effectiveProjectedX = 0.5d * origProjectedX - 0.5d * ROOT3 * origProjectedY; + effectiveProjectedY = 0.5d * ROOT3 * origProjectedX + 0.5d * origProjectedY; + effectiveOffsetFace += 6; //shift 14->20 & 15->21 + } + + dstPts[dstOff + 0] = effectiveProjectedX + CENTER_MAP[effectiveOffsetFace].x; + dstPts[dstOff + 1] = effectiveProjectedY + CENTER_MAP[effectiveOffsetFace].y; + } + + if (anyFailed) { + throw this.getBulkExceptionForSelf(); + } + } + } + + protected static class ToGeo extends AbstractToGeoMathTransform2D { + protected final Cached cacheCache; + + public ToGeo(@NonNull ParameterValueGroup parameters, @NonNull Cached cacheCache) { + super(parameters); + + this.cacheCache = cacheCache; + } + + protected void inverseTriangleTransform(double x, double y, Vector3d dst) { + inverseTriangleTransformNewton(x, y, dst); + } + + protected void inverseTriangleTransformDerivative(CACHE cache, double x, double y, Matrix3x2 dst) { + inverseTriangleTransformNewtonDerivative(cache, x, y, dst); + } + + @Override + public Matrix2 transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws TransformException { + final double origX = srcPts[srcOff + 0]; + final double origY = srcPts[srcOff + 1]; + + if (isInvalidCoordinates(origX, origY)) { //propagate NaN coordinates immediately + throw this.getSingleExceptionForSelf(); + } + + double x = origX; + double y = origY; + + final int face = findTriangleGrid(x, y); + if (face < 0) { + throw this.getSingleExceptionForSelf(); + } + x -= CENTER_MAP[face].x; + y -= CENTER_MAP[face].y; + + //deal with bounds of special snowflakes + switch (face) { + case 15: // if (x > 0 && x > y * TerraUtils.ROOT3) throw ...; + if (x <= y * TerraUtils.ROOT3) { + break; + } + //fallthrough + case 14: // if (x > 0) throw ...; + if (x > 0) { + throw this.getSingleExceptionForSelf(); + } + break; + case 21: // if (x < 0 || -y * TerraUtils.ROOT3 > x) throw ...; + if (x < 0) { + throw this.getSingleExceptionForSelf(); + } + //fallthrough + case 20: // if (-y * TerraUtils.ROOT3 > x) throw ...; + if (-y * TerraUtils.ROOT3 > x) { + throw this.getSingleExceptionForSelf(); + } + break; + } + + //flip triangle to upright orientation (if not already) + x *= FLIP_TRIANGLE_FACTOR[face]; + y *= FLIP_TRIANGLE_FACTOR[face]; + + CACHE cache = this.cacheCache.get(); + + Vector2d spherical = cache.spherical; + Vector3d cartesian = cache.cartesian; + Vector3d rotated = cache.rotated; + + //invert triangle transform + this.inverseTriangleTransform(x, y, rotated); + + //apply inverse rotation matrix (move triangle from template triangle to correct position on globe) + TMatrices.multiplyFast(INVERSE_ROTATION_MATRICES[face], rotated, cartesian); + + if (dstPts != null) { + //convert back to geo coordinates + TerraUtils.cartesian2Spherical(cartesian.x, cartesian.y, cartesian.z, spherical); + + //spherical -> geographic conversion is handled afterwards by the affine transform + dstPts[dstOff + 0] = spherical.x; + dstPts[dstOff + 1] = spherical.y; + } + if (!derivate) { + return null; + } + + Matrix3x2 rotatedDerivative = cache.rotatedDerivative; + Matrix3x2 cartesianDerivative = cache.cartesianDerivative; + Matrix2x3 sphericalDerivative = cache.projectedDerivative; + Matrix2 totalDerivative = cache.totalDerivative; + + this.inverseTriangleTransformDerivative(cache, x, y, rotatedDerivative); + TMatrices.multiplyFast(INVERSE_ROTATION_MATRICES[face], rotatedDerivative, cartesianDerivative); + TerraUtils.cartesian2SphericalDerivative(cartesian.x, cartesian.y, cartesian.z, sphericalDerivative); + TMatrices.multiplyFast(sphericalDerivative, cartesianDerivative, totalDerivative); + + //flip triangle to correct orientation + TMatrices.scaleFast(totalDerivative, FLIP_TRIANGLE_FACTOR[face], totalDerivative); + + return totalDerivative.clone(); + } + + @Override + public void transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, int numPts) throws TransformException { + int srcInc = 2; + int dstInc = 2; + + //noinspection ArrayEquality + if (srcPts == dstPts) { + switch (IterationStrategy.suggest(srcOff, 2, dstOff, 2, numPts)) { + case ASCENDING: { + break; + } + case DESCENDING: { + srcOff += 2 * (numPts - 1); + dstOff += 2 * (numPts - 1); + srcInc -= 4; + dstInc -= 4; + break; + } + default: { + //TODO: use array allocator for this + srcPts = Arrays.copyOfRange(srcPts, srcOff, srcOff + numPts * 2); + srcOff = 0; + break; + } + } + } + + CACHE cache = this.cacheCache.get(); + + Vector2d spherical = cache.spherical; + Vector3d cartesian = cache.cartesian; + Vector3d rotated = cache.rotated; + + boolean anyFailed = false; + for (; --numPts >= 0; srcOff += srcInc, dstOff += dstInc) { + double x = srcPts[srcOff + 0]; + double y = srcPts[srcOff + 1]; + + int face; + + if (isInvalidCoordinates(x, y) || (face = findTriangleGrid(x, y)) < 0) { + //out of bounds/invalid, fill destination buffer with NaN values and keep going + fillNaN(dstPts, dstOff); + anyFailed = true; + continue; + } + + // + // the following is the same algorithm as above, but copied here for performance + // + + x -= CENTER_MAP[face].x; + y -= CENTER_MAP[face].y; + + //deal with bounds of special snowflakes + switch (face) { + case 15: // if (x > 0 && x > y * TerraUtils.ROOT3) throw ...; + if (x <= y * TerraUtils.ROOT3) { + break; + } + //fallthrough + case 14: // if (x > 0) throw ...; + if (x > 0) { + fillNaN(dstPts, dstOff); + anyFailed = true; + continue; + } + break; + case 21: // if (x < 0 || -y * TerraUtils.ROOT3 > x) throw ...; + if (x < 0) { + fillNaN(dstPts, dstOff); + anyFailed = true; + continue; + } + //fallthrough + case 20: // if (-y * TerraUtils.ROOT3 > x) throw ...; + if (-y * TerraUtils.ROOT3 > x) { + fillNaN(dstPts, dstOff); + anyFailed = true; + continue; + } + break; + } + + //flip triangle to upright orientation (if not already) + x *= FLIP_TRIANGLE_FACTOR[face]; + y *= FLIP_TRIANGLE_FACTOR[face]; + + //invert triangle transform + this.inverseTriangleTransform(x, y, rotated); + + //apply inverse rotation matrix (move triangle from template triangle to correct position on globe) + TMatrices.multiplyFast(INVERSE_ROTATION_MATRICES[face], rotated, cartesian); + + //convert back to geo coordinates + TerraUtils.cartesian2Spherical(cartesian.x, cartesian.y, cartesian.z, spherical); + + //spherical -> geographic conversion is handled afterwards by the affine transform + dstPts[dstOff + 0] = spherical.x; + dstPts[dstOff + 1] = spherical.y; + } + + if (anyFailed) { + throw this.getBulkExceptionForSelf(); + } + } + } } \ No newline at end of file diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/epsg/EPSG3785.java b/src/main/java/net/buildtheearth/terraplusplus/projection/epsg/EPSG3785.java new file mode 100644 index 00000000..16fbcef5 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/epsg/EPSG3785.java @@ -0,0 +1,89 @@ +package net.buildtheearth.terraplusplus.projection.epsg; + +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.projection.mercator.WebMercatorProjection; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import org.apache.sis.internal.referencing.AxisDirections; +import org.apache.sis.internal.referencing.ReferencingFactoryContainer; +import org.apache.sis.internal.referencing.provider.Mercator1SP; +import org.apache.sis.internal.referencing.provider.MercatorSpherical; +import org.apache.sis.internal.simple.SimpleExtent; +import org.apache.sis.measure.Units; +import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.apache.sis.parameter.DefaultParameterValueGroup; +import org.opengis.referencing.IdentifiedObject; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.cs.AxisDirection; +import org.opengis.referencing.cs.CoordinateSystemAxis; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.util.FactoryException; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; + +/** + * Implementation of the EPSG:3785 projection. + * + * @author DaPorkchop_ + * @see builder = ImmutableSortedMap.naturalOrder(); + for (Map.Entry entry : properties.entrySet()) { + builder.put(Integer.parseInt(entry.getKey().toString()), new AsciiString(entry.getValue().toString())); + } + return builder.build(); + }, ReferenceStrength.SOFT); + } + } + } + + return registry.get(); + } + + protected final int code; + + @Override + @JsonValue + public String toString() { + return "EPSG:" + this.code; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/CenteredMercatorProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/CenteredMercatorProjection.java index 01b03d1c..e05d1000 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/CenteredMercatorProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/CenteredMercatorProjection.java @@ -1,19 +1,40 @@ package net.buildtheearth.terraplusplus.projection.mercator; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import net.buildtheearth.terraplusplus.TerraConstants; +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import org.apache.sis.internal.referencing.AxisDirections; +import org.apache.sis.internal.referencing.ReferencingFactoryContainer; +import org.apache.sis.internal.referencing.provider.Mercator1SP; +import org.apache.sis.internal.referencing.provider.MercatorSpherical; +import org.apache.sis.internal.simple.SimpleExtent; +import org.apache.sis.measure.Units; +import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.apache.sis.parameter.DefaultParameterValueGroup; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.opengis.referencing.IdentifiedObject; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.cs.AxisDirection; +import org.opengis.referencing.cs.CoordinateSystemAxis; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.util.FactoryException; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; /** * Implementation of the Mercator projection, normalized between -1 and 1. + *

+ * DaPorkchop_ says: you dummies, this isn't actually proper Mercator on an ellipsoid; it's spherical Mercator (aka "Pseudo-Mercator"), and is + * identical to {@link WebMercatorProjection} except that it's normalized to a different range and flipped. * * @see WebMercatorProjection * @see Wikipedia's article on the Mercator projection */ @JsonDeserialize public class CenteredMercatorProjection implements GeographicProjection { - @Override public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException { OutOfProjectionBoundsException.checkInRange(x, y, 1, 1); @@ -23,6 +44,20 @@ public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException }; } + @Override + public Matrix2 toGeoDerivative(double x, double y) throws OutOfProjectionBoundsException { + OutOfProjectionBoundsException.checkInRange(x, y, 1, 1); + + double m00 = 180.0d; + double m01 = 0.0d; + double m10 = 0.0d; + + //https://www.wolframalpha.com/input?i=d%2Fdy+%28%28atan%28exp%28-y+*+pi%29%29+*+2+-+pi%2F2%29+*+2+-+pi+%2F+2%29+*+180+%2F+pi + double m11 = (-360.0d * Math.exp(y * Math.PI)) / (Math.exp(2.0d * Math.PI * y) + 1.0d); + + return new Matrix2(m00, m01, m10, m11); + } + @Override public double[] fromGeo(double longitude, double latitude) throws OutOfProjectionBoundsException { OutOfProjectionBoundsException.checkInRange(longitude, latitude, 180, WebMercatorProjection.LIMIT_LATITUDE); @@ -33,13 +68,22 @@ public double[] fromGeo(double longitude, double latitude) throws OutOfProjectio } @Override - public double[] bounds() { - return new double[]{ -1, -1, 1, 1 }; + public Matrix2 fromGeoDerivative(double longitude, double latitude) throws OutOfProjectionBoundsException { + OutOfProjectionBoundsException.checkInRange(longitude, latitude, 180, WebMercatorProjection.LIMIT_LATITUDE); + + double m00 = 1.0d / 180.0d; + double m01 = 0.0d; + double m10 = 0.0d; + + //https://www.wolframalpha.com/input?i=d%2Fdy+-%28log%28tan%28%28pi+%2F+2+%2B+%28y+%2F+180+*+pi%29%29+%2F+2%29%29%29+%2F+pi + double m11 = -1.0d / (360.0d * Math.cos((90.0d + latitude) * Math.PI / 360.0d) * Math.sin((90.0d + latitude) * Math.PI / 360.0d)); + + return new Matrix2(m00, m01, m10, m11); } @Override - public double metersPerUnit() { - return Math.cos(Math.toRadians(30)) * TerraConstants.EARTH_CIRCUMFERENCE / 2; //Accurate at about 30 degrees + public double[] bounds() { + return new double[]{ -1, -1, 1, 1 }; } @Override @@ -51,4 +95,32 @@ public boolean upright() { public String toString() { return "Mercator"; } + + @Override + @SneakyThrows(FactoryException.class) + public CoordinateReferenceSystem projectedCRS() { + ReferencingFactoryContainer factories = SISHelper.factories(); + + CoordinateSystemAxis[] axes = { + factories.getCSFactory().createCoordinateSystemAxis(ImmutableMap.of(IdentifiedObject.NAME_KEY, "Easting"), "X", AxisDirection.EAST, Units.METRE), + factories.getCSFactory().createCoordinateSystemAxis(ImmutableMap.of(IdentifiedObject.NAME_KEY, "Southing"), "Y", AxisDirection.SOUTH, Units.METRE), + }; + + DefaultParameterValueGroup parameters = new DefaultParameterValueGroup(factories.getMathTransformFactory().getDefaultParameters("Popular Visualisation Pseudo Mercator")); + parameters.getOrCreate(Mercator1SP.SCALE_FACTOR).setValue(4.990640467330674E-8d, Units.UNITY); + parameters.getOrCreate(MercatorSpherical.FALSE_EASTING).setValue(0.0d, Units.METRE); + parameters.getOrCreate(MercatorSpherical.FALSE_NORTHING).setValue(0.0d, Units.METRE); + + return factories.getCRSFactory().createProjectedCRS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, "WGS 84 / Reversed Axis Order / Terra++ Centered Mercator", + CoordinateOperation.DOMAIN_OF_VALIDITY_KEY, new SimpleExtent(new DefaultGeographicBoundingBox(-180d, 180d, -90d, 90d), null, null)), + TPP_GEO_CRS, + factories.getCoordinateOperationFactory().createDefiningConversion( + ImmutableMap.of(IdentifiedObject.NAME_KEY, "Terra++ Centered Mercator"), + factories.getCoordinateOperationFactory().getOperationMethod("Popular Visualisation Pseudo Mercator"), + parameters), + factories.getCSFactory().createCartesianCS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, AxisDirections.appendTo(new StringBuilder("Cartesian CS"), axes)), + axes[0], axes[1])); + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/TransverseMercatorProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/TransverseMercatorProjection.java index 368e51f2..55380b58 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/TransverseMercatorProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/TransverseMercatorProjection.java @@ -1,7 +1,7 @@ package net.buildtheearth.terraplusplus.projection.mercator; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; @@ -58,11 +58,6 @@ public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException return new double[]{ lon, lat }; } - @Override - public double metersPerUnit() { - return METERS_PER_UNIT; - } - @Override public String toString() { return "Transverse Mercator"; diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/WebMercatorProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/WebMercatorProjection.java index d1a620c6..683293f2 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/WebMercatorProjection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/mercator/WebMercatorProjection.java @@ -1,18 +1,32 @@ package net.buildtheearth.terraplusplus.projection.mercator; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.collect.ImmutableMap; +import lombok.AccessLevel; import lombok.Getter; +import lombok.SneakyThrows; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.MathUtils; - -import java.util.Collections; -import java.util.Map; - -import static net.daporkchop.lib.common.util.PValidation.*; +import net.buildtheearth.terraplusplus.projection.sis.WKTStandard; +import net.buildtheearth.terraplusplus.util.TerraUtils; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import org.apache.sis.internal.referencing.AxisDirections; +import org.apache.sis.internal.referencing.ReferencingFactoryContainer; +import org.apache.sis.internal.referencing.provider.Mercator1SP; +import org.apache.sis.internal.referencing.provider.MercatorSpherical; +import org.apache.sis.internal.simple.SimpleExtent; +import org.apache.sis.measure.Units; +import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.apache.sis.parameter.DefaultParameterValueGroup; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.opengis.referencing.IdentifiedObject; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.cs.AxisDirection; +import org.opengis.referencing.cs.CoordinateSystemAxis; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.util.FactoryException; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; /** * Implementation of the web Mercator projection, with projected space normalized between 0 and 2^zoom * 256. @@ -25,46 +39,70 @@ */ @JsonDeserialize public class WebMercatorProjection implements GeographicProjection { - public static final double LIMIT_LATITUDE = Math.toDegrees(2 * Math.atan(Math.pow(Math.E, Math.PI)) - Math.PI / 2); - @Getter(onMethod_ = { @JsonGetter }) - protected final int zoom; - - protected transient final double scaleTo; - protected transient final double scaleFrom; - - @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) - public WebMercatorProjection(@JsonProperty("zoom") Integer zoom) { - this.zoom = zoom != null ? notNegative(zoom, "zoom") : 0; - - this.scaleTo = 1.0d / (256 << this.zoom); - this.scaleFrom = 256 << this.zoom; - } + public static final double SCALE_FROM = 256.0d; + public static final double SCALE_TO = 1.0d / SCALE_FROM; @Override public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException { - if (x < 0 || y < 0 || x > this.scaleFrom || y > this.scaleFrom) { + if (x < 0 || y < 0 || x > SCALE_FROM || y > SCALE_FROM) { throw OutOfProjectionBoundsException.get(); } + return new double[]{ - Math.toDegrees(this.scaleTo * x * MathUtils.TAU - Math.PI), - Math.toDegrees(Math.atan(Math.exp(Math.PI - this.scaleTo * y * MathUtils.TAU)) * 2 - Math.PI / 2) + Math.toDegrees(SCALE_TO * x * TerraUtils.TAU - Math.PI), + Math.toDegrees(Math.atan(Math.exp(Math.PI - SCALE_TO * y * TerraUtils.TAU)) * 2 - Math.PI / 2) }; } + @Override + public Matrix2 toGeoDerivative(double x, double y) throws OutOfProjectionBoundsException { + if (x < 0 || y < 0 || x > SCALE_FROM || y > SCALE_FROM) { + throw OutOfProjectionBoundsException.get(); + } + + double m00 = Math.toDegrees(SCALE_TO * TerraUtils.TAU); + double m01 = 0.0d; + double m10 = 0.0d; + + //https://www.wolframalpha.com/input?i=deriv+%28atan%28exp%28pi+-+y+*+s+*+2+*+pi%29%29+*+2+-+pi%2F2%29+*+180+%2F+pi + double m11 = (-720.0d * SCALE_TO * Math.exp(TerraUtils.TAU * SCALE_TO * y + Math.PI)) / (Math.exp(4.0d * Math.PI * SCALE_TO * y) + Math.exp(2.0d * Math.PI)); + + return new Matrix2(m00, m01, m10, m11); + } + @Override public double[] fromGeo(double longitude, double latitude) throws OutOfProjectionBoundsException { OutOfProjectionBoundsException.checkInRange(longitude, latitude, 180, LIMIT_LATITUDE); return new double[]{ - this.scaleFrom * (Math.toRadians(longitude) + Math.PI) / MathUtils.TAU, - this.scaleFrom * (Math.PI - Math.log(Math.tan((Math.PI / 2 + Math.toRadians(latitude)) / 2))) / MathUtils.TAU + SCALE_FROM * (Math.toRadians(longitude) + Math.PI) / TerraUtils.TAU, + SCALE_FROM * (Math.PI - Math.log(Math.tan((Math.PI / 2 + Math.toRadians(latitude)) / 2))) / TerraUtils.TAU }; } + @Override + public Matrix2 fromGeoDerivative(double longitude, double latitude) throws OutOfProjectionBoundsException { + OutOfProjectionBoundsException.checkInRange(longitude, latitude, 180, LIMIT_LATITUDE); + + double m00 = SCALE_FROM * Math.toRadians(1.0d) / TerraUtils.TAU; + double m01 = 0.0d; + double m10 = 0.0d; + + //https://www.wolframalpha.com/input?i=d%2Fdl+s+*+%28pi+-+log%28tan%28%28pi+%2F+2+%2B+%28l+%2F180+*+pi%29%29+%2F+2%29%29%29+%2F+%282+*+pi%29 + double m11 = -SCALE_FROM / (720.0d * Math.cos((90.0d + latitude) * Math.PI / 360.0d) * Math.sin((90.0d + latitude) * Math.PI / 360.0d)); + + return new Matrix2(m00, m01, m10, m11); + } + @Override public double[] bounds() { - return new double[]{ 0, 0, this.scaleFrom, this.scaleFrom }; + return new double[]{ 0, 0, SCALE_FROM, SCALE_FROM }; + } + + @Override + public double[] boundsGeo() { + return new double[]{ -180.0d, -LIMIT_LATITUDE, 180.0d, LIMIT_LATITUDE }; } @Override @@ -73,17 +111,35 @@ public boolean upright() { } @Override - public double metersPerUnit() { - return 100000; + @SneakyThrows(FactoryException.class) + public CoordinateReferenceSystem projectedCRS() { + ReferencingFactoryContainer factories = SISHelper.factories(); + + CoordinateSystemAxis[] axes = { + factories.getCSFactory().createCoordinateSystemAxis(ImmutableMap.of(IdentifiedObject.NAME_KEY, "Easting"), "X", AxisDirection.EAST, Units.METRE), + factories.getCSFactory().createCoordinateSystemAxis(ImmutableMap.of(IdentifiedObject.NAME_KEY, "Southing"), "Y", AxisDirection.SOUTH, Units.METRE), + }; + + DefaultParameterValueGroup parameters = new DefaultParameterValueGroup(factories.getMathTransformFactory().getDefaultParameters("Popular Visualisation Pseudo Mercator")); + parameters.getOrCreate(Mercator1SP.SCALE_FACTOR).setValue(6.388019798183263E-6d, Units.UNITY); + parameters.getOrCreate(MercatorSpherical.FALSE_EASTING).setValue(128.0d, Units.METRE); + parameters.getOrCreate(MercatorSpherical.FALSE_NORTHING).setValue(-128.0d, Units.METRE); + + return factories.getCRSFactory().createProjectedCRS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, "WGS 84 / Reversed Axis Order / Terra++ Web Mercator", + CoordinateOperation.DOMAIN_OF_VALIDITY_KEY, new SimpleExtent(new DefaultGeographicBoundingBox(-180d, 180d, -85.06, 85.06), null, null)), + TPP_GEO_CRS, + factories.getCoordinateOperationFactory().createDefiningConversion( + ImmutableMap.of(IdentifiedObject.NAME_KEY, "Terra++ Web Mercator"), + factories.getCoordinateOperationFactory().getOperationMethod("Popular Visualisation Pseudo Mercator"), + parameters), + factories.getCSFactory().createCartesianCS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, AxisDirections.appendTo(new StringBuilder("Cartesian CS"), axes)), + axes[0], axes[1])); } @Override public String toString() { return "Web Mercator"; } - - @Override - public Map properties() { - return Collections.singletonMap("zoom", this.zoom); - } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/AbstractOperationMethod.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/AbstractOperationMethod.java new file mode 100644 index 00000000..0f1b6267 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/AbstractOperationMethod.java @@ -0,0 +1,88 @@ +package net.buildtheearth.terraplusplus.projection.sis; + +import com.google.common.collect.ImmutableMap; +import lombok.NonNull; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.projection.sis.transform.AbstractFromGeoMathTransform2D; +import net.buildtheearth.terraplusplus.projection.sis.transform.AbstractToGeoMathTransform2D; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.daporkchop.lib.common.util.PorkUtil; +import net.daporkchop.lib.unsafe.PUnsafe; +import org.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.referencing.operation.DefaultOperationMethod; +import org.apache.sis.referencing.operation.transform.MathTransformProvider; +import org.opengis.parameter.GeneralParameterDescriptor; +import org.opengis.parameter.InvalidParameterNameException; +import org.opengis.parameter.InvalidParameterValueException; +import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransformFactory; +import org.opengis.referencing.operation.NoninvertibleTransformException; +import org.opengis.util.FactoryException; + +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +public abstract class AbstractOperationMethod extends DefaultOperationMethod implements MathTransformProvider { + public AbstractOperationMethod(@NonNull ParameterDescriptorGroup parameters) { + super(ImmutableMap.of(NAME_KEY, parameters.getName()), parameters); + } + + public AbstractOperationMethod(@NonNull String name, @NonNull GeneralParameterDescriptor... parameters) { + this(new ParameterBuilder() + .addName(SISHelper.tppOperationIdentifier(name)) + .createGroup(parameters)); + } + + public static abstract class ForLegacyProjection extends AbstractOperationMethod { + private static final Class CONCATENATEDTRANSFORM_CLASS = PorkUtil.classForName("org.apache.sis.referencing.operation.transform.ConcatenatedTransform"); + private static final long CONCATENATEDTRANSFORM_INVERSE_OFFSET = PUnsafe.pork_getOffset(CONCATENATEDTRANSFORM_CLASS, "inverse"); + + public ForLegacyProjection(@NonNull ParameterDescriptorGroup parameters) { + super(parameters); + } + + public ForLegacyProjection(@NonNull String name, @NonNull GeneralParameterDescriptor... parameters) { + super(name, parameters); + } + + protected abstract AbstractFromGeoMathTransform2D createBaseTransform(ParameterValueGroup parameters) throws InvalidParameterNameException, ParameterNotFoundException, InvalidParameterValueException; + + @Override + @SneakyThrows(NoninvertibleTransformException.class) + public MathTransform createMathTransform(MathTransformFactory factory, ParameterValueGroup parameters) throws InvalidParameterNameException, ParameterNotFoundException, InvalidParameterValueException, FactoryException { + AbstractFromGeoMathTransform2D fromGeoBase = this.createBaseTransform(parameters); + MathTransform fromGeoComplete = fromGeoBase.completeTransform(factory); + + AbstractToGeoMathTransform2D toGeoBase = fromGeoBase.inverse(); + MathTransform toGeoComplete = toGeoBase.completeTransform(factory); + + if (fromGeoBase == fromGeoComplete) { //no normalization is happening + checkState(toGeoBase == toGeoComplete, "fromGeo transform isn't normalized, but toGeo is?!?"); + } else if (CONCATENATEDTRANSFORM_CLASS.isInstance(fromGeoComplete)) { //the transform has been concatenated with its normalization and denormalization transforms + checkState(CONCATENATEDTRANSFORM_CLASS.isInstance(toGeoComplete), "fromGeo transform isn't a ConcatenatedTransform, but toGeo is?!?"); + + if (PUnsafe.getObject(fromGeoComplete, CONCATENATEDTRANSFORM_INVERSE_OFFSET) == null) { //the transform's inverse instance hasn't been set yet + checkState(PUnsafe.getObject(toGeoComplete, CONCATENATEDTRANSFORM_INVERSE_OFFSET) == null, "fromGeo ConcatenatedTransform doesn't have an inverse, but toGeo does?!?"); + + //set inverses + PUnsafe.putObject(fromGeoComplete, CONCATENATEDTRANSFORM_INVERSE_OFFSET, toGeoComplete); + PUnsafe.putObject(toGeoComplete, CONCATENATEDTRANSFORM_INVERSE_OFFSET, fromGeoComplete); + } else { + checkState(fromGeoComplete.inverse() == toGeoComplete, "fromGeo's inverse is different than toGeo!"); + } + } else { + throw new IllegalStateException(PorkUtil.className(fromGeoComplete) + ": " + fromGeoComplete); + } + + checkState(fromGeoComplete.inverse() == toGeoComplete); + checkState(toGeoComplete.inverse() == fromGeoComplete); + + return fromGeoComplete; + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/AbstractSISMigratedGeographicProjection.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/AbstractSISMigratedGeographicProjection.java new file mode 100644 index 00000000..0c51b0bd --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/AbstractSISMigratedGeographicProjection.java @@ -0,0 +1,50 @@ +package net.buildtheearth.terraplusplus.projection.sis; + +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import org.apache.sis.internal.referencing.AxisDirections; +import org.apache.sis.internal.referencing.ReferencingFactoryContainer; +import org.apache.sis.internal.simple.SimpleExtent; +import org.apache.sis.measure.Units; +import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.opengis.referencing.IdentifiedObject; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.cs.AxisDirection; +import org.opengis.referencing.cs.CoordinateSystemAxis; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.util.FactoryException; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; + +/** + * @author DaPorkchop_ + */ +public abstract class AbstractSISMigratedGeographicProjection implements GeographicProjection { + @Override + @SneakyThrows(FactoryException.class) + public CoordinateReferenceSystem projectedCRS() { + String name = this.toString(); + double[] bounds = this.boundsGeo(); + + ReferencingFactoryContainer factories = SISHelper.factories(); + + CoordinateSystemAxis[] axes = { + factories.getCSFactory().createCoordinateSystemAxis(ImmutableMap.of(IdentifiedObject.NAME_KEY, "Easting"), "X", AxisDirection.EAST, Units.METRE), + factories.getCSFactory().createCoordinateSystemAxis(ImmutableMap.of(IdentifiedObject.NAME_KEY, "Northing"), "Y", AxisDirection.NORTH, Units.METRE), + }; + + return factories.getCRSFactory().createProjectedCRS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, "WGS 84 / Reversed Axis Order / Terra++ " + name, + CoordinateOperation.DOMAIN_OF_VALIDITY_KEY, new SimpleExtent(new DefaultGeographicBoundingBox(bounds[0], bounds[2], bounds[1], bounds[3]), null, null)), + TPP_GEO_CRS, + factories.getCoordinateOperationFactory().createDefiningConversion( + ImmutableMap.of(IdentifiedObject.NAME_KEY, "Terra++ " + name), + factories.getCoordinateOperationFactory().getOperationMethod("Terra++ " + name), + factories.getMathTransformFactory().getDefaultParameters("Terra++" + name)), + factories.getCSFactory().createCartesianCS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, AxisDirections.appendTo(new StringBuilder("Cartesian CS"), axes)), + axes[0], axes[1])); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/SISProjectionWrapper.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/SISProjectionWrapper.java new file mode 100644 index 00000000..0bf8be78 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/SISProjectionWrapper.java @@ -0,0 +1,259 @@ +package net.buildtheearth.terraplusplus.projection.sis; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.NonNull; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.control.AdvancedEarthGui; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.daporkchop.lib.common.function.exception.EConsumer; +import org.apache.sis.geometry.DirectPosition2D; +import org.apache.sis.geometry.Envelopes; +import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.internal.referencing.AxisDirections; +import org.apache.sis.parameter.DefaultParameterValueGroup; +import org.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.transform.AbstractMathTransform; +import org.apache.sis.referencing.operation.transform.DomainDefinition; +import org.opengis.geometry.Envelope; +import org.opengis.metadata.extent.Extent; +import org.opengis.metadata.extent.GeographicBoundingBox; +import org.opengis.metadata.extent.GeographicExtent; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.cs.AxisDirection; +import org.opengis.referencing.cs.CoordinateSystem; +import org.opengis.referencing.cs.CoordinateSystemAxis; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; + +import java.text.ParseException; +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +@JsonDeserialize +@Getter +public final class SISProjectionWrapper implements GeographicProjection, GeographicProjection.FastProjectedCRS { + private static boolean canInvokeNoArgsConstructor() { + for (StackTraceElement element : new Throwable().getStackTrace()) { + if (AdvancedEarthGui.class.getName().equals(element.getClassName())) { + return true; + } + } + return false; + } + + private final CoordinateReferenceSystem geoCRS = TPP_GEO_CRS; + private final CoordinateReferenceSystem projectedCRS; + + private final MathTransform toGeo; + private final MathTransform fromGeo; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public SISProjectionWrapper( + @JsonProperty(value = "standard") WKTStandard standard, + @JsonProperty(value = "crs") String crs) throws ParseException { + this((standard != null && crs != null) || !canInvokeNoArgsConstructor() + ? (CoordinateReferenceSystem) Objects.requireNonNull(standard, "missing required creator property 'standard'").parse(Objects.requireNonNull(crs, "missing required creator property 'crs'")) + : TPP_GEO_CRS); + } + + public SISProjectionWrapper(@NonNull CoordinateReferenceSystem projectedCRS) { + this.projectedCRS = projectedCRS; + + this.toGeo = SISHelper.findOperation(this.projectedCRS, this.geoCRS).getMathTransform(); + this.fromGeo = SISHelper.findOperation(this.geoCRS, this.projectedCRS).getMathTransform(); + } + + @JsonGetter("standard") + private WKTStandard standard() { + return WKTStandard.WKT2_2015; + } + + @JsonGetter("crs") + public String getProjectedCRSAsWKT() { + return this.standard().format(this.projectedCRS); + } + + @Override + @SneakyThrows(TransformException.class) + public double[] toGeo(double x, double y) throws OutOfProjectionBoundsException { + double[] point = { x, y }; + this.toGeo.transform(point, 0, point, 0, 1); + return point; + } + + @Override + @SneakyThrows(TransformException.class) + public double[] fromGeo(double longitude, double latitude) throws OutOfProjectionBoundsException { + double[] point = { longitude, latitude }; + this.fromGeo.transform(point, 0, point, 0, 1); + return point; + } + + @Override + @SneakyThrows(TransformException.class) + public Matrix2 toGeoDerivative(double x, double y) throws OutOfProjectionBoundsException { + return Matrix2.castOrCopy(this.toGeo.derivative(new DirectPosition2D(x, y))); + } + + @Override + @SneakyThrows(TransformException.class) + public Matrix2 fromGeoDerivative(double longitude, double latitude) throws OutOfProjectionBoundsException { + return Matrix2.castOrCopy(this.fromGeo.derivative(new DirectPosition2D(longitude, latitude))); + } + + private Optional tryExtractBoundsFromAxes(@NonNull CoordinateSystem cs, boolean allowInfinity) { + checkArg(cs.getDimension() == 2); + + CoordinateSystemAxis longitudeAxis = cs.getAxis(0); + CoordinateSystemAxis latitudeAxis = cs.getAxis(1); + + if (!allowInfinity && (Double.isInfinite(longitudeAxis.getMinimumValue()) || Double.isInfinite(longitudeAxis.getMaximumValue()) + || Double.isInfinite(latitudeAxis.getMinimumValue()) || Double.isInfinite(latitudeAxis.getMaximumValue()))) { + return Optional.empty(); + } + + if (AxisDirections.absolute(longitudeAxis.getDirection()) == AxisDirection.NORTH) { + CoordinateSystemAxis tmp = longitudeAxis; + longitudeAxis = latitudeAxis; + latitudeAxis = tmp; + } + + if (AxisDirections.absolute(longitudeAxis.getDirection()) != AxisDirection.EAST + || AxisDirections.absolute(latitudeAxis.getDirection()) != AxisDirection.NORTH) { + return Optional.empty(); + } + + return Optional.of(new double[]{ + longitudeAxis.getMinimumValue(), latitudeAxis.getMinimumValue(), + longitudeAxis.getMaximumValue(), latitudeAxis.getMaximumValue(), + }); + } + + @Override + public double[] boundsGeo() { + Extent extent = this.projectedCRS.getDomainOfValidity(); + if (extent != null) { + Collection geographicExtents = extent.getGeographicElements(); + checkState(geographicExtents.size() == 1, "unexpected number of geographic extents: '%s'", geographicExtents.size()); + + for (GeographicExtent geographicExtent : geographicExtents) { + GeographicBoundingBox boundingBox = (GeographicBoundingBox) geographicExtent; + return new double[]{ + boundingBox.getWestBoundLongitude(), + boundingBox.getSouthBoundLatitude(), + boundingBox.getEastBoundLongitude(), + boundingBox.getNorthBoundLatitude(), + }; + } + } + + return this.tryExtractBoundsFromAxes(this.geoCRS.getCoordinateSystem(), false).orElseGet(GeographicProjection.super::boundsGeo); + } + + @Override + @SneakyThrows(TransformException.class) + public double[] bounds() { //TODO: remove or fix this + GeneralEnvelope initialGeoEnvelope = new GeneralEnvelope(this.geoCRS); + initialGeoEnvelope.setEnvelope(this.boundsGeo()); + + DomainDefinition geoDomainDefinition = new DomainDefinition(); + geoDomainDefinition.intersect(initialGeoEnvelope); + + DomainDefinition projDomainDefinition = new DomainDefinition(); + + if (this.fromGeo instanceof AbstractMathTransform) { + ((AbstractMathTransform) this.fromGeo).getDomain(geoDomainDefinition) + .ifPresent((EConsumer) geoEnvelope -> projDomainDefinition.intersect(Envelopes.transform(this.fromGeo, geoEnvelope))); + } + + if (this.toGeo instanceof AbstractMathTransform) { + Envelope envelope = ((AbstractMathTransform) this.toGeo).getDomain(projDomainDefinition).orElse(null); + if (envelope != null) { + return new double[]{ + envelope.getMinimum(0), + envelope.getMinimum(1), + envelope.getMaximum(0), + envelope.getMaximum(1), + }; + } + } + + Extent extent = this.projectedCRS.getDomainOfValidity(); + if (extent != null) { + Collection geographicExtents = extent.getGeographicElements(); + checkState(geographicExtents.size() == 1, "unexpected number of geographic extents: '%s'", geographicExtents.size()); + + for (GeographicExtent geographicExtent : geographicExtents) { + Envelope envelope = Envelopes.transform(this.fromGeo, new GeneralEnvelope((GeographicBoundingBox) geographicExtent)); + return new double[]{ + envelope.getMinimum(0), + envelope.getMinimum(1), + envelope.getMaximum(0), + envelope.getMaximum(1), + }; + } + } + + try { //TODO: this is pretty gross + double[] boundsGeo = this.boundsGeo(); + + //get max in by using extreme coordinates + double[] bounds = new double[4]; + + System.arraycopy(this.fromGeo(boundsGeo[0], boundsGeo[1]), 0, bounds, 0, 2); + System.arraycopy(this.fromGeo(boundsGeo[2], boundsGeo[3]), 0, bounds, 2, 2); + + if (bounds[0] > bounds[2]) { + double t = bounds[0]; + bounds[0] = bounds[2]; + bounds[2] = t; + } + + if (bounds[1] > bounds[3]) { + double t = bounds[1]; + bounds[1] = bounds[3]; + bounds[3] = t; + } + + return bounds; + } catch (OutOfProjectionBoundsException e) { + throw new IllegalStateException(this.toString()); + } + } + + @Override + public ParameterValueGroup parameters() { + ParameterDescriptor standardParameter = new ParameterBuilder().setRequired(true) + .addName("standard") + .createEnumerated(WKTStandard.class, WKTStandard.values(), WKTStandard.WKT2_2015); + + ParameterDescriptor crsParameter = new ParameterBuilder().setRequired(true) + .addName("crs") + .create(String.class, this.standard().format(TPP_GEO_CRS)); + + ParameterDescriptorGroup descriptors = new ParameterBuilder().addName("wkt").createGroup(standardParameter, crsParameter); + DefaultParameterValueGroup parameters = new DefaultParameterValueGroup(descriptors); + + parameters.getOrCreate(standardParameter).setValue(this.standard()); + parameters.getOrCreate(crsParameter).setValue(this.getProjectedCRSAsWKT()); + + return parameters; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WKTStandard.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WKTStandard.java new file mode 100644 index 00000000..b9327e3e --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WKTStandard.java @@ -0,0 +1,75 @@ +package net.buildtheearth.terraplusplus.projection.sis; + +import lombok.NonNull; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; +import org.apache.sis.internal.referencing.ReferencingFactoryContainer; +import org.apache.sis.io.wkt.Convention; +import org.apache.sis.io.wkt.KeywordCase; +import org.apache.sis.io.wkt.KeywordStyle; +import org.apache.sis.io.wkt.Symbols; +import org.apache.sis.io.wkt.WKTFormat; +import org.opengis.referencing.crs.CRSFactory; +import org.opengis.referencing.cs.CSFactory; +import org.opengis.referencing.datum.DatumFactory; +import org.opengis.referencing.operation.CoordinateOperationFactory; +import org.opengis.referencing.operation.MathTransformFactory; + +import java.text.ParseException; +import java.util.Locale; +import java.util.TimeZone; + +/** + * @author DaPorkchop_ + */ +public enum WKTStandard { + /** + * WKT2:2015 (ISO 19162:2015) + */ + WKT2_2015, + ; + + static { + { + WKTFormat baseFormat = new WKTFormat(Locale.ROOT, TimeZone.getDefault()); + baseFormat.setKeywordCase(KeywordCase.UPPER_CASE); + baseFormat.setKeywordStyle(KeywordStyle.SHORT); + baseFormat.setConvention(Convention.WKT2); + baseFormat.setSymbols(Symbols.SQUARE_BRACKETS); + baseFormat.setIndentation(WKTFormat.SINGLE_LINE); + WKT2_2015.setFormat(baseFormat); + } + } + + private Cached format; + + private void setFormat(@NonNull WKTFormat baseFormat) { + this.format = Cached.threadLocal(() -> { + ReferencingFactoryContainer factories = SISHelper.factories(); + + //clone the base WKTFormat instance, and configure it to use the thread-local factory instances from SISHelper.factories() + WKTFormat format = baseFormat.clone(); + format.setFactory(CRSFactory.class, factories.getCRSFactory()); + format.setFactory(CSFactory.class, factories.getCSFactory()); + format.setFactory(DatumFactory.class, factories.getDatumFactory()); + format.setFactory(MathTransformFactory.class, factories.getMathTransformFactory()); + format.setFactory(CoordinateOperationFactory.class, factories.getCoordinateOperationFactory()); + return format; + }, ReferenceStrength.SOFT); + } + + public Object parse(@NonNull String wkt) throws ParseException { + return this.format.get().parseObject(wkt); + } + + @SneakyThrows(ParseException.class) + public Object parseUnchecked(@NonNull String wkt) { + return this.parse(wkt); + } + + public String format(@NonNull Object object) { + return this.format.get().format(object); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WrappedProjectionMapTransform.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WrappedProjectionMapTransform.java new file mode 100644 index 00000000..0733add9 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WrappedProjectionMapTransform.java @@ -0,0 +1,136 @@ +package net.buildtheearth.terraplusplus.projection.sis; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.NonNull; +import lombok.With; +import net.buildtheearth.terraplusplus.config.GlobalParseRegistries; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.GeographicProjectionHelper; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.TerraConstants; +import org.apache.sis.geometry.Envelope2D; +import org.apache.sis.parameter.DefaultParameterValueGroup; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.transform.AbstractMathTransform2D; +import org.apache.sis.referencing.operation.transform.DomainDefinition; +import org.apache.sis.util.ComparisonMode; +import org.opengis.geometry.Envelope; +import org.opengis.parameter.InvalidParameterValueException; +import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.MathTransform2D; +import org.opengis.referencing.operation.TransformException; + +import java.util.Optional; + +import static net.buildtheearth.terraplusplus.projection.sis.WrappedProjectionOperationMethod.*; + +/** + * @author DaPorkchop_ + */ +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class WrappedProjectionMapTransform extends AbstractMathTransform2D { + private final GeographicProjection projection; + + private final DefaultParameterValueGroup params; + private transient volatile WrappedProjectionMapTransform inverse; + + @With(AccessLevel.PRIVATE) + private final boolean fromGeo; + + public WrappedProjectionMapTransform(@NonNull ParameterValueGroup params) { + this.params = new DefaultParameterValueGroup(params); + + String typeName = this.params.stringValue(PARAMETER_TYPE); + Class type = GlobalParseRegistries.PROJECTIONS.get(typeName); + if (type == null) { + throw new InvalidParameterValueException("unknown projection type: \"" + typeName + '"', PARAMETER_TYPE.getName().getCode(), typeName); + } + + String jsonArgs = this.params.stringValue(PARAMETER_JSON_ARGS); + try { + this.projection = TerraConstants.JSON_MAPPER.readValue(jsonArgs, type); + } catch (JsonProcessingException e) { + throw new InvalidParameterValueException("invalid projection arguments for type \"" + type + "\": \"" + jsonArgs + '"', PARAMETER_JSON_ARGS.getName().getCode(), jsonArgs); + } + + this.fromGeo = true; + } + + @Override + public Optional getDomain(@NonNull DomainDefinition criteria) throws TransformException { + double[] bounds = this.fromGeo ? this.projection.boundsGeo() : this.projection.bounds(); + + Envelope2D envelope = new Envelope2D(); + envelope.add(bounds[0], bounds[1]); + envelope.add(bounds[2], bounds[3]); + return Optional.of(criteria.result().map(result -> envelope.createUnion(new Envelope2D(result))).orElse(envelope)); + } + + private double[] transform(double x, double y) throws OutOfProjectionBoundsException { + return this.fromGeo ? this.projection.fromGeo(x, y) : this.projection.toGeo(x, y); + } + + private Matrix2 derivative(double x, double y) throws OutOfProjectionBoundsException { + //return this.fromGeo ? this.projection.fromGeoDerivative(x, y) : this.projection.toGeoDerivative(x, y); + return GeographicProjectionHelper.defaultDerivative(this.projection, x, y, this.fromGeo); + } + + @Override + public Matrix2 transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws TransformException { + double srcX = srcPts[srcOff + 0]; + double srcY = srcPts[srcOff + 1]; + + if (dstPts != null) { //may be null if only the derivative is requested + double[] result00 = this.transform(srcX, srcY); + + dstPts[dstOff + 0] = result00[0]; + dstPts[dstOff + 1] = result00[1]; + } + + if (!derivate) { + return null; + } + + return this.derivative(srcX, srcY); + } + + @Override + public MathTransform2D inverse() { + WrappedProjectionMapTransform inverse = this.inverse; + if (inverse == null) { + synchronized (this) { + if ((inverse = this.inverse) == null) { + inverse = this.inverse = this.withFromGeo(!this.fromGeo); + inverse.inverse = this; + } + } + } + return inverse; + } + + @Override + public ParameterDescriptorGroup getParameterDescriptors() { + return PARAMETERS; + } + + @Override + public ParameterValueGroup getParameterValues() { + return this.params; + } + + @Override + public boolean equals(Object object, ComparisonMode mode) { + return object instanceof WrappedProjectionMapTransform + && super.equals(object, mode) + && this.projection.equals(((WrappedProjectionMapTransform) object).projection) + && this.fromGeo == ((WrappedProjectionMapTransform) object).fromGeo; + } + + @Override + protected int computeHashCode() { + return (super.computeHashCode() * 31 + this.projection.hashCode()) * 31 + Boolean.hashCode(this.fromGeo); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WrappedProjectionOperationMethod.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WrappedProjectionOperationMethod.java new file mode 100644 index 00000000..f71828e0 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/WrappedProjectionOperationMethod.java @@ -0,0 +1,51 @@ +package net.buildtheearth.terraplusplus.projection.sis; + +import com.google.common.collect.ImmutableMap; +import org.apache.sis.metadata.iso.citation.Citations; +import org.apache.sis.parameter.DefaultParameterValueGroup; +import org.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.referencing.ImmutableIdentifier; +import org.apache.sis.referencing.operation.DefaultOperationMethod; +import org.apache.sis.referencing.operation.transform.MathTransformProvider; +import org.opengis.parameter.InvalidParameterNameException; +import org.opengis.parameter.InvalidParameterValueException; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransformFactory; +import org.opengis.util.FactoryException; + +import java.util.Map; + +/** + * @author DaPorkchop_ + */ +public final class WrappedProjectionOperationMethod extends DefaultOperationMethod implements MathTransformProvider { + private static final Map PROPERTIES = ImmutableMap.of( + NAME_KEY, new ImmutableIdentifier(Citations.fromName("Terra++"), "Terra++", "Terra++ Internal Projection")); + + public static final ParameterDescriptor PARAMETER_TYPE = new ParameterBuilder() + .addName("type") + .setRequired(true) + .create(String.class, null); + + public static final ParameterDescriptor PARAMETER_JSON_ARGS = new ParameterBuilder() + .addName("json_args") + .setRequired(false) + .create(String.class, "{}"); + + static final ParameterDescriptorGroup PARAMETERS = new ParameterBuilder() + .addName((ImmutableIdentifier) PROPERTIES.get(NAME_KEY)) + .createGroup(PARAMETER_TYPE, PARAMETER_JSON_ARGS); + + public WrappedProjectionOperationMethod() { + super(PROPERTIES, PARAMETERS); + } + + @Override + public MathTransform createMathTransform(MathTransformFactory factory, ParameterValueGroup parameters) throws InvalidParameterNameException, ParameterNotFoundException, InvalidParameterValueException, FactoryException { + return new WrappedProjectionMapTransform(parameters); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractFromGeoMathTransform2D.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractFromGeoMathTransform2D.java new file mode 100644 index 00000000..1bb0ba94 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractFromGeoMathTransform2D.java @@ -0,0 +1,32 @@ +package net.buildtheearth.terraplusplus.projection.sis.transform; + +import lombok.Getter; +import lombok.NonNull; +import org.apache.sis.geometry.Envelope2D; +import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.apache.sis.referencing.operation.transform.DomainDefinition; +import org.opengis.geometry.Envelope; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.TransformException; + +import java.util.Optional; + +/** + * @author DaPorkchop_ + */ +@Getter +public abstract class AbstractFromGeoMathTransform2D extends AbstractNormalizedMathTransform2D { + private final AbstractToGeoMathTransform2D inverse; + + public AbstractFromGeoMathTransform2D(@NonNull ParameterValueGroup contextualParameters, @NonNull AbstractToGeoMathTransform2D inverse) { + super(contextualParameters); + + this.inverse = inverse; + inverse.setInverse(this); + } + + @Override + public Optional getDomain(@NonNull DomainDefinition criteria) throws TransformException { + return Optional.of(new Envelope2D(new DefaultGeographicBoundingBox(-180.0d, 180.0d, -90.0d, 90.0d))); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractNormalizedMathTransform2D.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractNormalizedMathTransform2D.java new file mode 100644 index 00000000..317e0fb6 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractNormalizedMathTransform2D.java @@ -0,0 +1,109 @@ +package net.buildtheearth.terraplusplus.projection.sis.transform; + +import lombok.NonNull; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.transform.AbstractMathTransform2D; +import org.apache.sis.referencing.operation.transform.ContextualParameters; +import org.opengis.parameter.GeneralParameterValue; +import org.opengis.parameter.ParameterValue; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransformFactory; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; + +import java.util.Arrays; + +/** + * @author DaPorkchop_ + */ +public abstract class AbstractNormalizedMathTransform2D extends AbstractMathTransform2D { + private final ContextualParameters contextualParameters; + private boolean configuredMatrices; + + private volatile OutOfProjectionBoundsException cachedBulkException; + + public AbstractNormalizedMathTransform2D(@NonNull ParameterValueGroup contextualParameters) { + this.contextualParameters = new ContextualParameters(contextualParameters.getDescriptor(), 2, 2); + + //copy parameters into our ContextualParameters instance + for (GeneralParameterValue v : contextualParameters.values()) { + ParameterValue value = (ParameterValue) v; + if (value.getUnit() != null) { + this.contextualParameters.parameter(value.getDescriptor().getName().getCode()).setValue(value.doubleValue(), value.getUnit()); + } else { + this.contextualParameters.parameter(value.getDescriptor().getName().getCode()).setValue(value.getValue()); + } + } + } + + protected abstract void configureMatrices(ContextualParameters contextualParameters, MatrixSIS normalize, MatrixSIS denormalize); + + public synchronized MathTransform completeTransform(MathTransformFactory factory) throws FactoryException { + if (!this.configuredMatrices) { + this.configuredMatrices = true; + this.configureMatrices(this.contextualParameters, + this.contextualParameters.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION), + this.contextualParameters.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION)); + } + + return this.contextualParameters.completeTransform(factory, this); + } + + @Override + protected ContextualParameters getContextualParameters() { + return this.contextualParameters; + } + + @Override + public abstract Matrix2 transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws TransformException; + + protected static boolean isLongitudeLatitudeInRange(double longitude, double latitude) { + return Math.abs(longitude) <= 180.0d && Math.abs(latitude) <= 90.0d; + } + + protected static boolean isInvalidCoordinates(double x, double y) { + return Double.isNaN(x) || Double.isNaN(y); + } + + /** + * Gets an {@link OutOfProjectionBoundsException} to be thrown when this transform fails to project an individual point. + *

+ * The object's {@link OutOfProjectionBoundsException#getLastCompletedTransform() last completed transform} will be equal to {@code null}. + */ + protected final OutOfProjectionBoundsException getSingleExceptionForSelf() { + return OutOfProjectionBoundsException.get(); + } + + /** + * Gets an {@link OutOfProjectionBoundsException} to be thrown when this transform fails to project some number of points of a bulk transformation, + * such that all failed points have a value of {@link Double#NaN NaN} in the destination array. + *

+ * The object's {@link OutOfProjectionBoundsException#getLastCompletedTransform() last completed transform} will be equal to {@code this}. + */ + protected final OutOfProjectionBoundsException getBulkExceptionForSelf() { + if (OutOfProjectionBoundsException.FAST) { //lazily allocate a new exception instance if required, or rethrow existing cached exception + OutOfProjectionBoundsException exception = this.cachedBulkException; + if (exception != null) { + return exception; + } + + exception = new OutOfProjectionBoundsException(); + exception.setLastCompletedTransform(this); + this.cachedBulkException = exception; + return exception; + } else { //always create a new exception + OutOfProjectionBoundsException exception = new OutOfProjectionBoundsException(); + exception.setLastCompletedTransform(this); + return exception; + } + } + + protected static void fillNaN(double[] dstPts, int dstOff) { + if (dstPts != null) { + Arrays.fill(dstPts, dstOff, dstOff + 2, Double.NaN); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractToGeoMathTransform2D.java b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractToGeoMathTransform2D.java new file mode 100644 index 00000000..75c8c49b --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/sis/transform/AbstractToGeoMathTransform2D.java @@ -0,0 +1,33 @@ +package net.buildtheearth.terraplusplus.projection.sis.transform; + +import lombok.Getter; +import lombok.NonNull; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.transform.ContextualParameters; +import org.opengis.parameter.ParameterValueGroup; + +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +@Getter +public abstract class AbstractToGeoMathTransform2D extends AbstractNormalizedMathTransform2D { + private AbstractFromGeoMathTransform2D inverse; + + public AbstractToGeoMathTransform2D(@NonNull ParameterValueGroup contextualParameters) { + super(contextualParameters); + } + + void setInverse(@NonNull AbstractFromGeoMathTransform2D inverse) { + checkState(this.inverse == null); + checkArg(inverse.inverse() == this); + this.inverse = inverse; + } + + @Override + protected void configureMatrices(ContextualParameters contextualParameters, MatrixSIS normalize, MatrixSIS denormalize) { + contextualParameters.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION).setMatrix(this.inverse().getContextualParameters().getMatrix(ContextualParameters.MatrixRole.INVERSE_DENORMALIZATION)); + contextualParameters.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION).setMatrix(this.inverse().getContextualParameters().getMatrix(ContextualParameters.MatrixRole.INVERSE_NORMALIZATION)); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/FlipHorizontalProjectionTransform.java b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/FlipHorizontalProjectionTransform.java index 55556140..9563a75b 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/FlipHorizontalProjectionTransform.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/FlipHorizontalProjectionTransform.java @@ -5,6 +5,10 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.opengis.referencing.cs.CoordinateSystemAxis; /** * Mirrors the warped projection horizontally. @@ -48,4 +52,14 @@ public double[] bounds() { public String toString() { return "Horizontal Flip (" + super.delegate + ')'; } + + @Override + protected String toSimpleString() { + return "Horizontal Flip"; + } + + @Override + protected MatrixSIS affineMatrix() { + return Matrices.createAffine(new Matrix2(-1.0d, 0.0d, 0.0d, 1.0d), null); + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/FlipVerticalProjectionTransform.java b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/FlipVerticalProjectionTransform.java index 57e53fc9..ba0cd5ac 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/FlipVerticalProjectionTransform.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/FlipVerticalProjectionTransform.java @@ -5,6 +5,10 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.opengis.referencing.cs.CoordinateSystemAxis; /** * Mirrors the warped projection vertically. @@ -48,4 +52,14 @@ public double[] bounds() { public String toString() { return "Vertical Flip (" + super.delegate + ')'; } + + @Override + protected String toSimpleString() { + return "Vertical Flip"; + } + + @Override + protected MatrixSIS affineMatrix() { + return Matrices.createAffine(new Matrix2(1.0d, 0.0d, 0.0d, -1.0d), null); + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/OffsetProjectionTransform.java b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/OffsetProjectionTransform.java index f869665e..1d870992 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/OffsetProjectionTransform.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/OffsetProjectionTransform.java @@ -8,6 +8,10 @@ import lombok.Getter; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import org.apache.sis.geometry.DirectPosition2D; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.opengis.referencing.cs.CoordinateSystemAxis; /** * Applies a simple translation to the projected space, such that: @@ -62,4 +66,14 @@ public double[] fromGeo(double longitude, double latitude) throws OutOfProjectio public String toString() { return "Offset (" + super.delegate + ") by " + this.dx + ", " + this.dy; } + + @Override + protected String toSimpleString() { + return "Offset by " + this.dx + ", " + this.dy; + } + + @Override + protected MatrixSIS affineMatrix() { + return Matrices.createAffine(null, new DirectPosition2D(this.dx, this.dy)); + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/ProjectionTransform.java b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/ProjectionTransform.java index 4a6cb9eb..9bda1865 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/ProjectionTransform.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/ProjectionTransform.java @@ -1,13 +1,33 @@ package net.buildtheearth.terraplusplus.projection.transform; import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.collect.ImmutableMap; import lombok.Getter; +import lombok.SneakyThrows; import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.daporkchop.lib.common.util.PorkUtil; +import org.apache.sis.internal.referencing.ReferencingFactoryContainer; +import org.apache.sis.internal.referencing.provider.Affine; +import org.apache.sis.measure.Units; +import org.apache.sis.referencing.operation.matrix.Matrix3; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.opengis.referencing.IdentifiedObject; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.cs.AxisDirection; +import org.opengis.referencing.cs.CartesianCS; +import org.opengis.referencing.cs.CoordinateSystem; +import org.opengis.referencing.cs.CoordinateSystemAxis; +import org.opengis.referencing.cs.EllipsoidalCS; +import org.opengis.util.FactoryException; + +import java.util.stream.IntStream; /** * Warps a Geographic projection and applies a transformation to it. */ -@Getter(onMethod_ = { @JsonGetter }) +@Getter(onMethod_ = { @JsonGetter, @JsonSerialize(as = GeographicProjection.class) }) public abstract class ProjectionTransform implements GeographicProjection { protected final GeographicProjection delegate; @@ -24,12 +44,65 @@ public boolean upright() { } @Override - public double[] bounds() { - return this.delegate.bounds(); + public abstract double[] bounds(); + + @Override + public double[] boundsGeo() { + return this.delegate.boundsGeo(); + } + + protected abstract String toSimpleString(); + + protected abstract MatrixSIS affineMatrix(); + + protected CoordinateSystemAxis[] transformAxes(CoordinateSystemAxis[] axes) { + return axes; } @Override - public double metersPerUnit() { - return this.delegate.metersPerUnit(); + @SneakyThrows(FactoryException.class) + public CoordinateReferenceSystem projectedCRS() { + /*GeographicProjection proj = this; + Matrix3 affineMatrix = new Matrix3(); + do { + affineMatrix.multiply(((ProjectionTransform) proj).affineMatrix()); + } while ((proj = ((ProjectionTransform) proj).delegate()) instanceof ProjectionTransform);*/ + + CoordinateReferenceSystem baseCRS = SISHelper.projectedCRS(this.delegate()); + + String simpleString = "Terra++ " + this.toSimpleString(); + MatrixSIS affineMatrix = this.affineMatrix(); + if (affineMatrix.isIdentity()) { + return baseCRS; + } + + ReferencingFactoryContainer factories = SISHelper.factories(); + + CoordinateSystemAxis[] axes = IntStream.range(0, baseCRS.getCoordinateSystem().getDimension()).mapToObj(baseCRS.getCoordinateSystem()::getAxis).toArray(CoordinateSystemAxis[]::new); + CoordinateSystemAxis[] transformedAxes = this.transformAxes(axes); + + factories.getMathTransformFactory().createAffineTransform(affineMatrix); + + CoordinateSystem cs; + if (baseCRS.getCoordinateSystem() instanceof CartesianCS) { + cs = factories.getCSFactory().createCartesianCS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, baseCRS.getCoordinateSystem().getName().getCode() + " / " + simpleString), + transformedAxes[0], transformedAxes[1]); + } else if (baseCRS.getCoordinateSystem() instanceof EllipsoidalCS) { + cs = factories.getCSFactory().createEllipsoidalCS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, baseCRS.getCoordinateSystem().getName().getCode() + " / " + simpleString), + transformedAxes[0], transformedAxes[1]); + } else { + throw new IllegalArgumentException(PorkUtil.className(baseCRS.getCoordinateSystem())); + } + + return factories.getCRSFactory().createDerivedCRS( + ImmutableMap.of(IdentifiedObject.NAME_KEY, baseCRS.getName().getCode() + " / " + simpleString), + baseCRS, + factories.getCoordinateOperationFactory().createDefiningConversion( + ImmutableMap.of(IdentifiedObject.NAME_KEY, simpleString), + Affine.getProvider(2, 2, true), + Affine.parameters(affineMatrix)), + cs); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/ScaleProjectionTransform.java b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/ScaleProjectionTransform.java index 229270e7..8ad5a41e 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/ScaleProjectionTransform.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/ScaleProjectionTransform.java @@ -8,6 +8,11 @@ import lombok.Getter; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.Matrix3; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.opengis.referencing.cs.CoordinateSystemAxis; /** * Scales the warps projection's projected space up or down. @@ -67,12 +72,17 @@ public double[] bounds() { } @Override - public double metersPerUnit() { - return this.delegate.metersPerUnit() / Math.sqrt((this.x * this.x + this.y * this.y) / 2); //TODO: better transform + public String toString() { + return "Scale (" + super.delegate + ") by " + this.x + ", " + this.y; } @Override - public String toString() { - return "Scale (" + super.delegate + ") by " + this.x + ", " + this.y; + protected String toSimpleString() { + return "Scale by " + this.x + ", " + this.y; + } + + @Override + protected MatrixSIS affineMatrix() { + return Matrices.createAffine(new Matrix2(this.x, 0.0d, 0.0d, this.y), null); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/SwapAxesProjectionTransform.java b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/SwapAxesProjectionTransform.java index 8013e88f..d09734ed 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/projection/transform/SwapAxesProjectionTransform.java +++ b/src/main/java/net/buildtheearth/terraplusplus/projection/transform/SwapAxesProjectionTransform.java @@ -5,6 +5,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.opengis.referencing.cs.CoordinateSystemAxis; /** * Inverses the warped projection such that x becomes y and y becomes x. @@ -45,4 +48,19 @@ public double[] bounds() { public String toString() { return "Swap Axes(" + super.delegate + ')'; } + + @Override + protected String toSimpleString() { + return "Swap Axes"; + } + + @Override + protected MatrixSIS affineMatrix() { + return Matrices.createDimensionSelect(3, new int[]{ 1, 0, 2 }); + } + + @Override + protected CoordinateSystemAxis[] transformAxes(CoordinateSystemAxis[] axes) { + return new CoordinateSystemAxis[] { axes[0], axes[1] }; + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/provider/EarthWorldProvider.java b/src/main/java/net/buildtheearth/terraplusplus/provider/EarthWorldProvider.java index fbac11db..7a9f08ab 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/provider/EarthWorldProvider.java +++ b/src/main/java/net/buildtheearth/terraplusplus/provider/EarthWorldProvider.java @@ -1,7 +1,7 @@ package net.buildtheearth.terraplusplus.provider; import net.buildtheearth.terraplusplus.EarthWorldType; -import net.buildtheearth.terraplusplus.generator.populate.SnowPopulator; +import net.buildtheearth.terraplusplus.generator.populate.PopulatorSnow; import net.minecraft.util.math.BlockPos; import net.minecraft.world.WorldProviderSurface; @@ -20,6 +20,6 @@ public boolean canSnowAt(BlockPos pos, boolean checkLight) { return super.canSnowAt(pos, checkLight); } - return SnowPopulator.canSnow(pos, this.world, false); + return PopulatorSnow.canSnow(pos, this.world, false); } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/provider/GenerationEventDenier.java b/src/main/java/net/buildtheearth/terraplusplus/provider/GenerationEventDenier.java index 021c7303..e3537d99 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/provider/GenerationEventDenier.java +++ b/src/main/java/net/buildtheearth/terraplusplus/provider/GenerationEventDenier.java @@ -5,12 +5,9 @@ import io.github.opencubicchunks.cubicchunks.api.worldgen.populator.event.DecorateCubeBiomeEvent; import io.github.opencubicchunks.cubicchunks.api.worldgen.populator.event.PopulateCubeEvent; import lombok.experimental.UtilityClass; -import net.buildtheearth.terraplusplus.TerraConstants; -import net.buildtheearth.terraplusplus.generator.EarthBiomeProvider; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.buildtheearth.terraplusplus.generator.EarthGenerator; import net.minecraft.world.World; -import net.minecraftforge.event.terraingen.DecorateBiomeEvent; -import net.minecraftforge.event.terraingen.PopulateChunkEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/CardinalDirection.java b/src/main/java/net/buildtheearth/terraplusplus/util/CardinalDirection.java index 5756df2d..1b776992 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/CardinalDirection.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/CardinalDirection.java @@ -1,7 +1,6 @@ package net.buildtheearth.terraplusplus.util; import lombok.Getter; -import net.buildtheearth.terraplusplus.TerraConstants; @Getter public enum CardinalDirection { diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/ChatUtil.java b/src/main/java/net/buildtheearth/terraplusplus/util/ChatUtil.java deleted file mode 100644 index fea5b0be..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/util/ChatUtil.java +++ /dev/null @@ -1,76 +0,0 @@ -package net.buildtheearth.terraplusplus.util; - -import net.buildtheearth.terraplusplus.TerraConstants; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.Style; -import net.minecraft.util.text.TextComponentString; -import net.minecraft.util.text.TextFormatting; - -/** - * A set of chat utilities for forge - * - * @author Noah Husby - */ -public class ChatUtil { - public static ITextComponent title() { - return new TextComponentString(TerraConstants.CHAT_PREFIX.replace("&", "\u00A7")); - } - - public static ITextComponent titleAndCombine(Object... objects) { - return combine(true, objects); - } - - public static ITextComponent combine(Object... objects) { - return combine(false, objects); - } - - public static ITextComponent combine(boolean title, Object... objects) { - ITextComponent textComponent = title ? title() : new TextComponentString(""); - StringBuilder builder = null; - TextFormatting lastFormat = null; - for (Object o : objects) { - if (o instanceof ITextComponent) { - if (builder != null) { - textComponent.appendSibling(new TextComponentString(builder.toString())); - builder = null; - } - - ITextComponent component = (ITextComponent) o; - if (component.getStyle().getColor() == null && lastFormat != null) { - component.setStyle(new Style().setColor(lastFormat)); - } - - textComponent.appendSibling(component); - } else { - if (o instanceof TextFormatting) { - lastFormat = (TextFormatting) o; - } - if (builder == null) { - builder = new StringBuilder(); - } - builder.append(o); - } - } - - if (builder != null) { - textComponent.appendSibling(new TextComponentString(builder.toString())); - } - return textComponent; - } - - public static ITextComponent getNotCC() { - return titleAndCombine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".error.notcc")); - } - - public static ITextComponent getNotTerra() { - return titleAndCombine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".error.noterra")); - } - - public static ITextComponent getNoPermission() { - return titleAndCombine(TextFormatting.RED, "You do not have permission to use this command"); - } - - public static ITextComponent getPlayerOnly() { - return titleAndCombine(TextFormatting.RED, TranslateUtil.translate(TerraConstants.MODID + ".error.playeronly")); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/CornerBoundingBox2d.java b/src/main/java/net/buildtheearth/terraplusplus/util/CornerBoundingBox2d.java index bcb0be31..53ab8609 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/CornerBoundingBox2d.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/CornerBoundingBox2d.java @@ -178,4 +178,14 @@ public double minZ() { public double maxZ() { return max(max(this.lat00, this.lat01), max(this.lat10, this.lat11)); } + + public double avgDegreesPerSample(int sizeX, int sizeZ) { + double x00_01 = abs(this.lon00 - this.lon01) / sizeX; + double x10_11 = abs(this.lon10 - this.lon11) / sizeX; + + double z00_01 = abs(this.lat00 - this.lat01) / sizeZ; + double z10_11 = abs(this.lat10 - this.lat11) / sizeZ; + + return (x00_01 + x10_11 + z00_01 + z10_11) * 0.25d; + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/InternHelper.java b/src/main/java/net/buildtheearth/terraplusplus/util/InternHelper.java new file mode 100644 index 00000000..7e1e7d9f --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/InternHelper.java @@ -0,0 +1,37 @@ +package net.buildtheearth.terraplusplus.util; + +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import lombok.NonNull; +import lombok.experimental.UtilityClass; + +import static net.daporkchop.lib.common.util.PorkUtil.*; + +/** + * @author DaPorkchop_ + */ +@SuppressWarnings("UnstableApiUsage") +@UtilityClass +public class InternHelper { + private static final Interner INTERNER = Interners.newWeakInterner(); + + public static T intern(@NonNull T instance) { + if (instance instanceof String) { + return uncheckedCast(((String) instance).intern()); + } + + return uncheckedCast(INTERNER.intern(instance)); + } + + public static String intern(@NonNull String instance) { + return instance.intern(); + } + + public static > T tryInternNullableInternable(T instance) { + return instance != null ? uncheckedCast(instance.intern()) : null; + } + + public static String tryInternNullableString(String instance) { + return instance != null ? instance.intern() : null; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/Internable.java b/src/main/java/net/buildtheearth/terraplusplus/util/Internable.java new file mode 100644 index 00000000..3699f184 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/Internable.java @@ -0,0 +1,16 @@ +package net.buildtheearth.terraplusplus.util; + +/** + * Represents a type which can be interned. + * + * @author DaPorkchop_ + */ +public interface Internable> { + /** + * Returns a canonical representation for this object. + * + * @return an object that has the same contents as this object, but is guaranteed to be from a pool of unique objects + * @see String#intern() + */ + I intern(); +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/MathUtils.java b/src/main/java/net/buildtheearth/terraplusplus/util/MathUtils.java deleted file mode 100644 index c4ff382c..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/util/MathUtils.java +++ /dev/null @@ -1,161 +0,0 @@ -package net.buildtheearth.terraplusplus.util; - -import lombok.experimental.UtilityClass; - -import static net.daporkchop.lib.common.util.PValidation.*; - -@UtilityClass -public class MathUtils { - /** - * Square root of 3 - */ - public static final double ROOT3 = Math.sqrt(3); - - /** - * Two times pi - */ - public static final double TAU = 2 * Math.PI; - - - /** - * Converts geographic latitude and longitude coordinates to spherical coordinates on a sphere of radius 1. - * - * @param geo - geographic coordinates as a double array of length 2, {longitude, latitude}, in degrees - * @return the corresponding spherical coordinates in radians: {longitude, colatitude} - */ - public static double[] geo2Spherical(double[] geo) { - double lambda = Math.toRadians(geo[0]); - double phi = Math.toRadians(90 - geo[1]); - return new double[]{ lambda, phi }; - } - - - /** - * Converts spherical coordinates to geographic coordinates on a sphere of radius 1. - * - * @param spherical - spherical coordinates in radians as a double array of length 2: {longitude, colatitude} - * @return the corresponding geographic coordinates in degrees: {longitude, latitude} - */ - public static double[] spherical2Geo(double[] spherical) { - double lon = Math.toDegrees(spherical[0]); - double lat = 90 - Math.toDegrees(spherical[1]); - return new double[]{ lon, lat }; - } - - - /** - * Converts spherical coordinates to Cartesian coordinates on a sphere of radius 1. - * - * @param spherical - spherical coordinates in radians as a double array of length 2: {longitude, colatitude} - * @return the corresponding Cartesian coordinates: {x, y, z} - */ - public static double[] spherical2Cartesian(double[] spherical) { - double sinphi = Math.sin(spherical[1]); - double x = sinphi * Math.cos(spherical[0]); - double y = sinphi * Math.sin(spherical[0]); - double z = Math.cos(spherical[1]); - return new double[]{ x, y, z }; - } - - /** - * Converts Cartesian coordinates to spherical coordinates on a sphere of radius 1. - * - * @param cartesian - Cartesian coordinates as double array of length 3: {x, y, z} - * @return the spherical coordinates of the corresponding normalized vector - */ - public static double[] cartesian2Spherical(double[] cartesian) { - double lambda = Math.atan2(cartesian[1], cartesian[0]); - double phi = Math.atan2(Math.sqrt(cartesian[0] * cartesian[0] + cartesian[1] * cartesian[1]), cartesian[2]); - return new double[]{ lambda, phi }; - } - - - /** - * TODO produceZYZRotationMatrix javadoc - * - * @param a - * @param b - * @param c - * @return - */ - public static double[][] produceZYZRotationMatrix(double a, double b, double c) { - - double sina = Math.sin(a); - double cosa = Math.cos(a); - double sinb = Math.sin(b); - double cosb = Math.cos(b); - double sinc = Math.sin(c); - double cosc = Math.cos(c); - - double[][] mat = new double[3][3]; - mat[0][0] = cosa * cosb * cosc - sinc * sina; - mat[0][1] = -sina * cosb * cosc - sinc * cosa; - mat[0][2] = cosc * sinb; - - mat[1][0] = sinc * cosb * cosa + cosc * sina; - mat[1][1] = cosc * cosa - sinc * cosb * sina; - mat[1][2] = sinc * sinb; - - mat[2][0] = -sinb * cosa; - mat[2][1] = sinb * sina; - mat[2][2] = cosb; - - return mat; - } - - /** - * Multiples the given matrix with the given vector. - * The matrix is assumed to be square and the vector is assumed to be of the same dimension as the matrix. - * - * @param matrix - the matrix as a n*n double array - * @param vector - the vector as double array of length n - * @return the result of the multiplication as an array of double on length n - */ - public static double[] matVecProdD(double[][] matrix, double[] vector) { - double[] result = new double[vector.length]; - for (int i = 0; i < result.length; i++) { - for (int j = 0; j < matrix[i].length; j++) { - result[i] += matrix[i][j] * vector[j]; - } - } - return result; - } - - /** - * Converts all values in a double array from degrees to radians - * - * @param arr - array to work on - */ - public static void toRadians(double[] arr) { - for(int i=0; i 0) { - res = val << shift; - checkState(res >> shift == val, "numeric overflow: val: %d, shift: %d", val, shift); - } else { - res = val >> -shift; - } - return res; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/TerraConstants.java b/src/main/java/net/buildtheearth/terraplusplus/util/TerraConstants.java new file mode 100644 index 00000000..47089515 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/TerraConstants.java @@ -0,0 +1,62 @@ +package net.buildtheearth.terraplusplus.util; + +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import lombok.experimental.UtilityClass; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.buildtheearth.terraplusplus.util.jackson.mixin.BiomeMixin; +import net.buildtheearth.terraplusplus.util.jackson.mixin.BlockStateMixin; +import net.daporkchop.lib.common.pool.array.ArrayAllocator; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; +import net.daporkchop.lib.common.util.PorkUtil; +import net.daporkchop.lib.unsafe.PUnsafe; +import net.minecraft.block.state.IBlockState; +import net.minecraft.world.biome.Biome; +import org.apache.sis.referencing.CommonCRS; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.crs.GeographicCRS; + +@UtilityClass +public class TerraConstants { + public final String MODID = "terraplusplus"; + public String VERSION = "(development_snapshot)"; + + public String CC_VERSION = "unknown"; + + public final String CHAT_PREFIX = "&2&lT++ &8&l> "; + public final String defaultCommandNode = MODID + ".command."; + public final String othersCommandNode = MODID + ".others"; + + public static final JsonMapper JSON_MAPPER = JsonMapper.builder() + .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true) + .configure(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS, true) + .configure(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS, true) + .configure(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS, true) + .configure(JsonReadFeature.ALLOW_TRAILING_COMMA, true) + .configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true) + .addMixIn(Biome.class, BiomeMixin.class) + .addMixIn(IBlockState.class, BlockStateMixin.class) + .build(); + + /** + * The {@link CoordinateReferenceSystem} used by Terra++ for geographic coordinates. + * + * @see SISHelper#tppGeoCrs() + */ + public static final GeographicCRS TPP_GEO_CRS = SISHelper.tppGeoCrs(); + + /** + * Earth's circumference around the equator, in meters. + */ + public final double EARTH_CIRCUMFERENCE = 40075017; + + /** + * Earth's circumference around the poles, in meters. + */ + public final double EARTH_POLAR_CIRCUMFERENCE = 40008000; + + public final double[] EMPTY_DOUBLE_ARRAY = PorkUtil.EMPTY_DOUBLE_ARRAY; + + public static final Cached> DOUBLE_ALLOC = Cached.threadLocal(() -> ArrayAllocator.pow2(double.class, ReferenceStrength.STRONG, 16), ReferenceStrength.SOFT); +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/TerraUtils.java b/src/main/java/net/buildtheearth/terraplusplus/util/TerraUtils.java new file mode 100644 index 00000000..dd1c893d --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/TerraUtils.java @@ -0,0 +1,871 @@ +package net.buildtheearth.terraplusplus.util; + +import com.google.common.collect.ImmutableList; +import lombok.NonNull; +import lombok.experimental.UtilityClass; +import net.daporkchop.lib.common.function.plain.TriFunction; +import net.daporkchop.lib.common.util.PorkUtil; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.translation.I18n; +import net.minecraftforge.fml.common.FMLCommonHandler; + +import javax.vecmath.Vector2d; +import javax.vecmath.Vector3d; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.Math.*; +import static net.daporkchop.lib.common.util.PValidation.*; +import static net.daporkchop.lib.common.util.PorkUtil.*; + +/** + * @author DaPorkchop_ + */ +@UtilityClass +public class TerraUtils { + /** + * Square root of 3 + */ + public static final double ROOT3 = Math.sqrt(3); + /** + * Two times pi + */ + public static final double TAU = 2 * Math.PI; + + public static ITextComponent translate(String key) { + if (FMLCommonHandler.instance().getMinecraftServerInstance().isSinglePlayer()) { + return new TextComponentTranslation(key); + } + return new TextComponentString(net.minecraft.util.text.translation.I18n.translateToLocal(key)); + } + + public static ITextComponent format(String key, Object... args) { + if (FMLCommonHandler.instance().getMinecraftServerInstance().isSinglePlayer()) { + return new TextComponentTranslation(key, args); + } + return new TextComponentString(I18n.translateToLocalFormatted(key, args)); + } + + /** + * Converts geographic latitude and longitude coordinates to spherical coordinates on a sphere of radius 1. + * + * @param geo - geographic coordinates as a double array of length 2, {longitude, latitude}, in degrees + * @return the corresponding spherical coordinates in radians: {longitude, colatitude} + */ + public static double[] geo2Spherical(double[] geo) { + double lambda = Math.toRadians(geo[0]); + double phi = Math.toRadians(90 - geo[1]); + return new double[]{ lambda, phi }; + } + + public static void geo2Spherical(Vector2d geo, Vector2d dst) { + geo2Spherical(geo.x, geo.y, dst); + } + + public static void geo2Spherical(double longitude, double latitude, Vector2d dst) { + dst.x = Math.toRadians(longitude); + dst.y = Math.toRadians(90.0d - latitude); + } + + /** + * Converts spherical coordinates to geographic coordinates on a sphere of radius 1. + * + * @param spherical - spherical coordinates in radians as a double array of length 2: {longitude, colatitude} + * @return the corresponding geographic coordinates in degrees: {longitude, latitude} + */ + public static double[] spherical2Geo(double[] spherical) { + double lon = Math.toDegrees(spherical[0]); + double lat = 90 - Math.toDegrees(spherical[1]); + return new double[]{ lon, lat }; + } + + public static void spherical2Geo(Vector2d spherical, Vector2d dst) { + spherical2Geo(spherical.x, spherical.y, dst); + } + + public static void spherical2Geo(double longitude, double colatitude, Vector2d dst) { + dst.x = Math.toDegrees(longitude); + dst.y = 90.0d - Math.toDegrees(colatitude); + } + + /** + * Converts spherical coordinates to Cartesian coordinates on a sphere of radius 1. + * + * @param spherical - spherical coordinates in radians as a double array of length 2: {longitude, colatitude} + * @return the corresponding Cartesian coordinates: {x, y, z} + */ + public static double[] spherical2Cartesian(double[] spherical) { + double sinphi = Math.sin(spherical[1]); + double x = sinphi * Math.cos(spherical[0]); + double y = sinphi * Math.sin(spherical[0]); + double z = Math.cos(spherical[1]); + return new double[]{ x, y, z }; + } + + public static void spherical2Cartesian(Vector2d spherical, Vector3d dst) { + spherical2Cartesian(spherical.x, spherical.y, dst); + } + + /** + * Converts spherical coordinates to Cartesian coordinates on a sphere of radius 1. + * + * @param longitude longitude in radians + * @param colatitude colatitude in radians + */ + public static void spherical2Cartesian(double longitude, double colatitude, Vector3d dst) { + double sinphi = Math.sin(colatitude); + dst.x = sinphi * Math.cos(longitude); + dst.y = sinphi * Math.sin(longitude); + dst.z = Math.cos(colatitude); + } + + public static void spherical2CartesianDerivative(double longitude, double colatitude, Matrix3x2 dst) { + double sinlon = Math.sin(longitude); + double coslon = Math.cos(longitude); + double sinlat = Math.sin(colatitude); + double coslat = Math.cos(colatitude); + + // https://www.wolframalpha.com/input?i=d%2Fdl+sin%28c%29+*+cos%28l%29 + dst.m00 = -sinlat * sinlon; + + // https://www.wolframalpha.com/input?i=d%2Fdc+sin%28c%29+*+cos%28l%29 + dst.m01 = coslat * coslon; + + // https://www.wolframalpha.com/input?i=d%2Fdl+sin%28c%29+*+sin%28l%29 + dst.m10 = sinlat * coslon; + + // https://www.wolframalpha.com/input?i=d%2Fdc+sin%28c%29+*+sin%28l%29 + dst.m11 = coslat * sinlon; + + // https://www.wolframalpha.com/input?i=d%2Fdl+cos%28c%29 + dst.m20 = 0.0d; + + // https://www.wolframalpha.com/input?i=d%2Fdc+cos%28c%29 + dst.m21 = -sinlat; + } + + /** + * Converts Cartesian coordinates to spherical coordinates on a sphere of radius 1. + * + * @param cartesian - Cartesian coordinates as double array of length 3: {x, y, z} + * @return the spherical coordinates of the corresponding normalized vector + */ + public static double[] cartesian2Spherical(double[] cartesian) { + double lambda = Math.atan2(cartesian[1], cartesian[0]); + double phi = Math.atan2(Math.sqrt(cartesian[0] * cartesian[0] + cartesian[1] * cartesian[1]), cartesian[2]); + return new double[]{ lambda, phi }; + } + + public static Vector2d cartesian2Spherical(Vector3d cartesian) { + Vector2d result = new Vector2d(); + cartesian2Spherical(cartesian.x, cartesian.y, cartesian.z, result); + return result; + } + + public static void cartesian2Spherical(double x, double y, double z, Vector2d dst) { + dst.x = Math.atan2(y, x); + dst.y = Math.atan2(Math.sqrt(x * x + y * y), z); + } + + public static void cartesian2SphericalDerivative(double x, double y, double z, Matrix2x3 dst) { + double xyLengthSq = x * x + y * y; + double xyzLengthSq = xyLengthSq + z * z; + + double xyLength = Math.sqrt(xyLengthSq); + + // https://www.wolframalpha.com/input?i=d%2Fdx+atan2%28y%2C+x%29 + dst.m00 = -y / xyLengthSq; + + // https://www.wolframalpha.com/input?i=d%2Fdy+atan2%28y%2C+x%29 + dst.m01 = x / xyLengthSq; + + // https://www.wolframalpha.com/input?i=d%2Fdz+atan2%28y%2C+x%29 + dst.m02 = 0.0d; + + // https://www.wolframalpha.com/input?i=d%2Fdx+atan2%28sqrt%28x%5E2%2By%5E2%29%2C+z%29 + dst.m10 = (x * z) / (xyLength * xyzLengthSq); + + // https://www.wolframalpha.com/input?i=d%2Fdy+atan2%28sqrt%28x%5E2%2By%5E2%29%2C+z%29 + dst.m11 = (y * z) / (xyLength * xyzLengthSq); + + // https://www.wolframalpha.com/input?i=d%2Fdz+atan2%28sqrt%28x%5E2%2By%5E2%29%2C+z%29 + dst.m12 = -xyLength / xyzLengthSq; + } + + /** + * TODO produceZYZRotationMatrix javadoc + * + * @param a + * @param b + * @param c + * @return + */ + public static Matrix3 produceZYZRotationMatrix(double a, double b, double c) { + + double sina = Math.sin(a); + double cosa = Math.cos(a); + double sinb = Math.sin(b); + double cosb = Math.cos(b); + double sinc = Math.sin(c); + double cosc = Math.cos(c); + + Matrix3 mat = new Matrix3(); + mat.m00 = cosa * cosb * cosc - sinc * sina; + mat.m01 = -sina * cosb * cosc - sinc * cosa; + mat.m02 = cosc * sinb; + + mat.m10 = sinc * cosb * cosa + cosc * sina; + mat.m11 = cosc * cosa - sinc * cosb * sina; + mat.m12 = sinc * sinb; + + mat.m20 = -sinb * cosa; + mat.m21 = sinb * sina; + mat.m22 = cosb; + + return mat; + } + + public static MatrixSIS matrixToSIS(double[][] matrix) { + int rows = matrix.length; + int cols = matrix[0].length; + MatrixSIS result = TMatrices.createZero(rows, cols); + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + result.setElement(row, col, matrix[row][col]); + } + } + return result; + } + + /** + * Multiples the given matrix with the given vector. + * The matrix is assumed to be square and the vector is assumed to be of the same dimension as the matrix. + * + * @param matrix - the matrix as a n*n double array + * @param vector - the vector as double array of length n + * @return the result of the multiplication as an array of double on length n + */ + public static double[] matVecProdD(double[][] matrix, double[] vector) { + double[] result = new double[vector.length]; + for (int i = 0; i < result.length; i++) { + for (int j = 0; j < matrix[i].length; j++) { + result[i] += matrix[i][j] * vector[j]; + } + } + return result; + } + + /** + * Converts all values in a double array from degrees to radians + * + * @param arr - array to work on + */ + public static void toRadians(double[] arr) { + for (int i = 0; i < arr.length; i++) { + arr[i] = Math.toRadians(arr[i]); + } + } + + /** + * Converts all values in a double array from radians to degrees + * + * @param arr - array to work on + */ + public static void toDegrees(double[] arr) { + for (int i = 0; i < arr.length; i++) { + arr[i] = Math.toDegrees(arr[i]); + } + } + + /** + * Squares the given value. + * + * @param d the value to square + * @return d² + */ + public static double sq(double d) { + return d * d; + } + + /** + * Right-shifts the given value by the given number of bits, safely handling negative shifts and checking for overflow. + * + * @param val the value + * @param shift the number of bits to shift by + * @return the shifted value + */ + public static int safeDirectionalShift(int val, int shift) { + int res; + if (shift == 0) { + res = val; + } else if (shift > 0) { + res = val << shift; + checkState(res >> shift == val, "numeric overflow: val: %d, shift: %d", val, shift); + } else { + res = val >> -shift; + } + return res; + } + + public static ITextComponent title() { + return new TextComponentString(TerraConstants.CHAT_PREFIX.replace("&", "\u00A7")); + } + + public static ITextComponent titleAndCombine(Object... objects) { + return combine(true, objects); + } + + public static ITextComponent combine(Object... objects) { + return combine(false, objects); + } + + public static ITextComponent combine(boolean title, Object... objects) { + ITextComponent textComponent = title ? title() : new TextComponentString(""); + StringBuilder builder = null; + TextFormatting lastFormat = null; + for (Object o : objects) { + if (o instanceof ITextComponent) { + if (builder != null) { + textComponent.appendSibling(new TextComponentString(builder.toString())); + builder = null; + } + + ITextComponent component = (ITextComponent) o; + if (component.getStyle().getColor() == null && lastFormat != null) { + component.setStyle(new Style().setColor(lastFormat)); + } + + textComponent.appendSibling(component); + } else { + if (o instanceof TextFormatting) { + lastFormat = (TextFormatting) o; + } + if (builder == null) { + builder = new StringBuilder(); + } + builder.append(o); + } + } + + if (builder != null) { + textComponent.appendSibling(new TextComponentString(builder.toString())); + } + return textComponent; + } + + public static ITextComponent getNotCC() { + return titleAndCombine(TextFormatting.RED, translate(TerraConstants.MODID + ".error.notcc")); + } + + public static ITextComponent getNotTerra() { + return titleAndCombine(TextFormatting.RED, translate(TerraConstants.MODID + ".error.noterra")); + } + + public static ITextComponent getNoPermission() { + return titleAndCombine(TextFormatting.RED, "You do not have permission to use this command"); + } + + public static ITextComponent getPlayerOnly() { + return titleAndCombine(TextFormatting.RED, translate(TerraConstants.MODID + ".error.playeronly")); + } + + /** + * Merges multiple {@link CompletableFuture}s together asynchronously. + * + * @param futures a {@link Stream} containing the {@link CompletableFuture}s to merge + * @param the type of value + * @return a {@link CompletableFuture} which will be notified once all of the futures have been completed + */ + public CompletableFuture> mergeFuturesAsync(@NonNull Stream> futures) { + CompletableFuture[] arr = uncheckedCast(futures.toArray(CompletableFuture[]::new)); + return CompletableFuture.allOf(arr).thenApply(unused -> Arrays.stream(arr).map(CompletableFuture::join).collect(Collectors.toList())); + } + + /** + * Compares two {@code double[]}s. + * + * @param v0 the first {@code double[]} + * @param v1 the second {@code double[]} + * @see Comparator#compare(Object, Object) + */ + public int compareDoubleArrays(@NonNull double[] v0, @NonNull double[] v1) { + int len0 = v0.length; + int len1 = v1.length; + + for (int i = 0, lim = min(len0, len1); i < lim; i++) { + int d = Double.compare(v0[i], v1[i]); + if (d != 0) { + return d; + } + } + + return len0 - len1; + } + + /** + * Converts the given {@code double} to {@code long} without loss of precision. + * + * @param value the {@code double} + * @return the given value as a {@code long} + * @throws ArithmeticException if the given value cannot be converted to {@code long} without loss of precision + */ + public static long toLongExact(double value) throws ArithmeticException { + if ((long) value != value) { + throw new ArithmeticException("loss of precision when converting to long"); + } + return (long) value; + } + + /** + * Converts the given {@link Number} to {@code long} without loss of precision. + * + * @param value the {@link Number} + * @return the given value as a {@code long} + * @throws ArithmeticException if the given value cannot be converted to {@code long} without loss of precision + */ + public static long toLongExact(@NonNull Number value) throws ArithmeticException { + if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) { + //these types can be converted to long without loss of precision + return value.longValue(); + } else if (value instanceof Double || value instanceof Float) { + //these types can be converted to double without loss of precision, and from there we can try to convert to long + return toLongExact(value.doubleValue()); + } else if (value instanceof BigDecimal) { + return ((BigDecimal) value).longValueExact(); + } else if (value instanceof BigInteger) { + return ((BigInteger) value).longValueExact(); + } else { + throw new IllegalArgumentException("unknown Number type: " + PorkUtil.className(value)); + } + } + + /** + * Converts the given {@code long} to {@code double} without loss of precision. + * + * @param value the {@code long} + * @return the given value as a {@code double} + * @throws ArithmeticException if the given value cannot be converted to {@code double} without loss of precision + */ + public static double toDoubleExact(long value) throws ArithmeticException { + if ((long) (double) value != value) { + throw new ArithmeticException("loss of precision when converting to double"); + } + return value; + } + + /** + * Converts the given {@link BigInteger} to {@code double} without loss of precision. + * + * @param value the {@link BigInteger} + * @return the given value as a {@code double} + * @throws ArithmeticException if the given value cannot be converted to {@code double} without loss of precision + */ + public static double toDoubleExact(@NonNull BigInteger value) throws ArithmeticException { + return toDoubleExact(value.longValueExact()); + } + + /** + * Converts the given {@link BigDecimal} to {@code double} without loss of precision. + * + * @param value the {@link BigDecimal} + * @return the given value as a {@code double} + * @throws ArithmeticException if the given value cannot be converted to {@code double} without loss of precision + */ + public static double toDoubleExact(@NonNull BigDecimal value) throws ArithmeticException { + //we could probably do some cool stuff like checking if the precision and scale are within the possible range, however this + // lazier approach is guaranteed to work correctly + double doubleValue = value.doubleValue(); + if (!BigDecimal.valueOf(doubleValue).equals(value)) { + throw new ArithmeticException("loss of precision when converting to double"); + } + return doubleValue; + } + + /** + * Converts the given {@link Number} to {@code double} without loss of precision. + * + * @param value the {@link Number} + * @return the given value as a {@code double} + * @throws ArithmeticException if the given value cannot be converted to {@code double} without loss of precision + */ + public static double toDoubleExact(@NonNull Number value) throws ArithmeticException { + if (value instanceof Double || value instanceof Integer || value instanceof Float + || value instanceof Short || value instanceof Byte) { + //these types can be converted to double without loss of precision + return value.doubleValue(); + } else if (value instanceof Long) { + return toDoubleExact(value.longValue()); + } else if (value instanceof BigDecimal) { + return toDoubleExact((BigDecimal) value); + } else if (value instanceof BigInteger) { + return toDoubleExact((BigInteger) value); + } else { + throw new IllegalArgumentException("unknown Number type: " + PorkUtil.className(value)); + } + } + + /** + * Converts the given {@link Number} to {@link BigDecimal} without loss of precision. + * + * @param value the {@link Number} + * @return the given value as a {@link BigDecimal} + * @throws ArithmeticException if the given value cannot be converted to {@link BigDecimal} without loss of precision + */ + public static BigDecimal toBigDecimalExact(@NonNull Number value) throws ArithmeticException { + if (value instanceof Double || value instanceof Float) { + //these types can be converted to double without loss of precision, and from there we can convert losslessly to BigDecimal + return BigDecimal.valueOf(value.doubleValue()); + } else if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) { + //these types can be converted to long without loss of precision, and from there we can convert losslessly to BigDecimal + return BigDecimal.valueOf(value.longValue()); + } else if (value instanceof BigDecimal) { + return (BigDecimal) value; + } else if (value instanceof BigInteger) { + return new BigDecimal((BigInteger) value); + } else { + throw new IllegalArgumentException("unknown Number type: " + PorkUtil.className(value)); + } + } + + /** + * Converts the given {@link Number} to {@link BigInteger} without loss of precision. + * + * @param value the {@link Number} + * @return the given value as a {@link BigInteger} + * @throws ArithmeticException if the given value cannot be converted to {@link BigInteger} without loss of precision + */ + public static BigInteger toBigIntegerExact(@NonNull Number value) throws ArithmeticException { + if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) { + //these types can be converted to long without loss of precision, and from there we can convert losslessly to BigInteger + return BigInteger.valueOf(value.longValue()); + } else if (value instanceof Double || value instanceof Float) { + //these types can be converted to double without loss of precision, from there we can try to convert to long, and + // if that succeeds we can convert losslessly to BigInteger + return BigInteger.valueOf(toLongExact(value.doubleValue())); + } else if (value instanceof BigDecimal) { + return ((BigDecimal) value).toBigIntegerExact(); + } else if (value instanceof BigInteger) { + return (BigInteger) value; + } else { + throw new IllegalArgumentException("unknown Number type: " + PorkUtil.className(value)); + } + } + + public static boolean isPrimitiveIntegerType(@NonNull Number value) { + return value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte; + } + + public static boolean isAnyIntegerType(@NonNull Number value) { + return isPrimitiveIntegerType(value) || value instanceof BigInteger; + } + + public static boolean isPrimitiveFloatingPointType(@NonNull Number value) { + return value instanceof Double || value instanceof Float; + } + + public static boolean isAnyFloatingPointType(@NonNull Number value) { + return isPrimitiveFloatingPointType(value) || value instanceof BigDecimal; + } + + public static boolean numbersEqual(@NonNull Number a, @NonNull Number b) { + checkArg(isAnyIntegerType(a) || isAnyFloatingPointType(a), "unknown Number type: %s", a.getClass()); + checkArg(isAnyIntegerType(b) || isAnyFloatingPointType(b), "unknown Number type: %s", b.getClass()); + + if (a.getClass() == b.getClass()) { + //they are both the same numeric type, compare using Object#equals(Object) + return a.equals(b); + } else if (isPrimitiveIntegerType(a) && isPrimitiveIntegerType(b)) { + //all primitive integral types can be converted to long without loss of precision + return a.longValue() == b.longValue(); + } else if (isPrimitiveFloatingPointType(a) && isPrimitiveFloatingPointType(b)) { + //all primitive floating-point types can be converted to double without loss of precision + return a.doubleValue() == b.doubleValue(); + } else if (isAnyIntegerType(a) && isAnyIntegerType(b)) { + //at least one of the two values is a BigInteger, so we'll convert both values to BigInteger and compare those (could be optimized more, i don't care) + return toBigIntegerExact(a).equals(toBigIntegerExact(b)); + } else { + //at least one of the two values is a BigDecimal, so we'll convert both values to BigDecimal and compare those (could be optimized more, i don't care) + return toBigDecimalExact(a).compareTo(toBigDecimalExact(b)) == 0; + } + } + + private static void addRangeTo(@NonNull ImmutableList src, int srcBegin, int srcEnd, @NonNull ImmutableList.Builder dst) { + if (srcBegin == srcEnd) { + return; + } else if (srcBegin == 0 && srcEnd == src.size()) { + dst.addAll(src); + return; + } + + for (int i = srcBegin; i < srcEnd; i++) { + dst.add(src.get(i)); + } + } + + private static void singleFlattenInto(@NonNull T value, @NonNull ImmutableList.Builder dst, @NonNull Function> flattener) { + Iterable flattenedValues = flattener.apply(value); + if (flattenedValues != null) { + flattenInto(flattenedValues, dst, flattener); + } else { + dst.add(value); + } + } + + private static void flattenInto(@NonNull Iterable src, @NonNull ImmutableList.Builder dst, @NonNull Function> flattener) { + for (T value : src) { + Iterable flattenedValues = flattener.apply(value); + if (flattenedValues != null) { + flattenInto(flattenedValues, dst, flattener); + } else { + dst.add(value); + } + } + } + + /** + * @param flattener a function which either returns an {@link Iterable} containing the new value(s) to insert in place of the existing + * value, or {@code null} to leave the existing value unmodified + */ + public static ImmutableList maybeFlatten(@NonNull ImmutableList origList, @NonNull Function> flattener) { + for (int i = 0; i < origList.size(); i++) { + Iterable flattenedValues = flattener.apply(origList.get(i)); + + if (flattenedValues != null) { + ImmutableList.Builder builder = ImmutableList.builder(); + + addRangeTo(origList, 0, i, builder); + flattenInto(flattenedValues, builder, flattener); + + while (++i < origList.size()) { + singleFlattenInto(origList.get(i), builder, flattener); + } + + return builder.build(); + } + } + + return origList; + } + + /** + * @param remapper a function which either returns the value to replace the existing value with + */ + public static ImmutableList maybeRemap(@NonNull ImmutableList origList, @NonNull Function remapper) { + for (int i = 0; i < origList.size(); i++) { + T origValue = origList.get(i); + T remappedValue = remapper.apply(origValue); + + if (origValue != remappedValue) { //the remapping function returned a different value, so the results have changed and we need to build a new list with the results + ImmutableList.Builder builder = ImmutableList.builder(); + + addRangeTo(origList, 0, i, builder); //append all the previous elements (which were unmodified) + builder.add(remappedValue); + + //remap and append all remaining elements + while (++i < origList.size()) { + builder.add(remapper.apply(origList.get(i))); + } + + return builder.build(); + } + } + + //no values were modified + return origList; + } + + /** + * @param shouldRemove a predicate which returns {@code true} if the value should be removed + */ + public static ImmutableList maybeRemove(@NonNull ImmutableList origList, @NonNull Predicate shouldRemove) { + for (int i = 0; i < origList.size(); i++) { + if (shouldRemove.test(origList.get(i))) { + ImmutableList.Builder builder = ImmutableList.builder(); + + addRangeTo(origList, 0, i, builder); //append all the previous elements (which were all kept) + while (++i < origList.size()) { + T origValue = origList.get(i); + if (!shouldRemove.test(origValue)) { + builder.add(origValue); + } + } + + return builder.build(); + } + } + + //no values were removed + return origList; + } + + /** + * @param merger a function which returns a single value as the result of merging the two given values, or {@code null} if both values should be kept + */ + public static ImmutableList maybeMerge2Neighbors(@NonNull ImmutableList origList, @NonNull BiFunction merger) { + if (origList.size() < 2) { //list is too small to do anything with + return origList; + } + + ImmutableList.Builder builder = null; + + // a: the neighboring element at the lower index, may be either the value of origList.get(bIndex - 1) or a merge output if the previous merger invocation + // actually merged something + // b: the neighboring element at the higher index, always the value of origList.get(bIndex) + + T a = origList.get(0); + T b; + int bIndex = 1; + + do { + b = origList.get(bIndex); + + T mergedValue = merger.apply(a, b); + + if (mergedValue != null) { //the two values were merged into a single result! + if (builder == null) { //this is the first time anything has been merged so far + //create a new builder (setting this will ensure that all subsequently encountered values will be written out to the builder, + // as we know that we won't be returning the original input list) + builder = ImmutableList.builder(); + + //append all the previous elements in the range [0, a) - none of them were able to be merged, so we can copy them in now that + // we know SOMETHING will change) + addRangeTo(origList, 0, bIndex - 1, builder); + } + + //set a to the merge result, so that we can immediately try to merge it again with the next value in the list (the value of b will + // be ignored, which is correct since it's now part of mergedValue, which is now a) + a = mergedValue; + } else { //we weren't able to merge the two values + if (builder != null) { //a previous merge has succeeded, so we should add the old value to the new list even though nothing changed + builder.add(a); + } + + //prepare to advance by one element + a = b; + } + } while (++bIndex < origList.size()); + + if (builder != null) { //at least one merge succeeded, add the last value of a and return the newly constructed list + return builder.add(a).build(); + } else { //nothing changed + return origList; + } + } + + /** + * @param merger a function which returns an {@link ImmutableList} containing the value(s) resulting from merging the three given values, or + * {@code null} if all three values should be kept + */ + public static ImmutableList maybeMerge3Neighbors(@NonNull ImmutableList origList, @NonNull TriFunction> merger) { + if (origList.size() < 3) { //list is too small to do anything with + return origList; + } + + ImmutableList.Builder builder = null; + + // a: the neighboring element at the lowest index, may be either the value of origList.get(cIndex - 2) or the first merge output if the previous merger invocation + // succeeded and produced one or two elements + // b: the neighboring element at the middle index, may be either the value of origList.get(cIndex - 1) or the second merge output if the previous merger invocation + // succeeded and produced exactly two elements + // c: the neighboring element at the highest index, always the value of origList.get(cIndex) + + T a = Objects.requireNonNull(origList.get(0)); + T b = Objects.requireNonNull(origList.get(1)); + T c; + int cIndex = 2; + + do { + c = Objects.requireNonNull(origList.get(cIndex)); + + ImmutableList mergedValues = merger.apply(a, b, c); + + if (mergedValues != null) { //the two values were merged into a single result! + if (builder == null) { //this is the first time anything has been merged so far + //create a new builder (setting this will ensure that all subsequently encountered values will be written out to the builder, + // as we know that we won't be returning the original input list) + builder = ImmutableList.builder(); + + //append all the previous elements in the range [0, a) - none of them were able to be merged, so we can copy them in now that + // we know SOMETHING will change) + addRangeTo(origList, 0, cIndex - 2, builder); + } + + switch (mergedValues.size()) { + case 0: //merging produced no outputs, skip everything and try to advance twice + a = ++cIndex < origList.size() ? Objects.requireNonNull(origList.get(cIndex)) : null; + b = ++cIndex < origList.size() ? Objects.requireNonNull(origList.get(cIndex)) : null; + //c will be loaded automatically during the next loop iteration, or not if there isn't enough space + break; + case 1: + //set a to the merge result, so that we can immediately try to merge it again with the next two values in the list (the values of b and c will + // be ignored, which is correct since it's now part of mergedValues, which is now a) + a = Objects.requireNonNull(mergedValues.get(0)); + b = ++cIndex < origList.size() ? Objects.requireNonNull(origList.get(cIndex)) : null; + //c will be loaded automatically during the next loop iteration, or not if there isn't enough space + break; + case 2: + a = Objects.requireNonNull(mergedValues.get(0)); + b = Objects.requireNonNull(mergedValues.get(1)); + //c will be loaded automatically during the next loop iteration, or not if there isn't enough space + break; + default: + throw new UnsupportedOperationException("merger returned " + mergedValues.size() + " elements!"); + } + } else { //we weren't able to merge the three values + if (builder != null) { //a previous merge has succeeded, so we should add the old value to the new list even though nothing changed + builder.add(a); + } + + //prepare to advance by one element + a = b; + b = c; + } + } while (++cIndex < origList.size()); + + if (builder != null) { //at least one merge succeeded, add the last value of a (and b, if necessary) and return the newly constructed list + + //a or b can only be null if the last loop iteration resulted in a merge producing one output, and there weren't enough input values left + // to load into (a and) b before terminating the loop + if (a != null) { + builder.add(a); + if (b != null) { + builder.add(b); + } + } + + return builder.build(); + } else { //nothing changed + return origList; + } + } + + /** + * {@link Internable#intern() Interns} the elements of the given {@link ImmutableList}. + * + * @return an {@link ImmutableList} containing the interned representations of the elements of the original list + */ + public static > ImmutableList internElements(@NonNull ImmutableList origList) { + return maybeRemap(origList, uncheckedCast((Function, Object>) Internable::intern)); + } + + /** + * Concatenates the given {@link ImmutableList}s. + */ + public static ImmutableList concat(@NonNull ImmutableList l, @NonNull ImmutableList r) { + return ImmutableList.builder().addAll(l).addAll(r).build(); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/TilePos.java b/src/main/java/net/buildtheearth/terraplusplus/util/TilePos.java index 74b926ff..59ebe6f5 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/TilePos.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/TilePos.java @@ -1,8 +1,12 @@ package net.buildtheearth.terraplusplus.util; +import io.github.opencubicchunks.cubicchunks.api.util.Coords; +import io.github.opencubicchunks.cubicchunks.api.util.CubePos; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NonNull; import lombok.ToString; +import net.minecraft.util.math.ChunkPos; /** * Representation of a tile position (a 2D position and a zoom level). @@ -17,6 +21,26 @@ public class TilePos { protected final int z; protected final int zoom; + public TilePos(@NonNull ChunkPos src) { + this(src.x, src.z, 0); + } + + public TilePos(@NonNull CubePos src) { + this(src.getX(), src.getZ(), 0); + } + + public int blockX() { + return Coords.cubeToMinBlock(this.x << this.zoom); + } + + public int blockZ() { + return Coords.cubeToMinBlock(this.z << this.zoom); + } + + public int sizeBlocks() { + return Coords.cubeToMinBlock(1 << this.zoom); + } + @Override public int hashCode() { return (int) (((3241L * 3457689L + this.x) * 8734625L + this.z) * 2873465L + this.zoom); diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/TranslateUtil.java b/src/main/java/net/buildtheearth/terraplusplus/util/TranslateUtil.java deleted file mode 100644 index 5771b184..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/util/TranslateUtil.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.buildtheearth.terraplusplus.util; - -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.TextComponentString; -import net.minecraft.util.text.TextComponentTranslation; -import net.minecraft.util.text.translation.I18n; -import net.minecraftforge.fml.common.FMLCommonHandler; - -public class TranslateUtil { - public static ITextComponent translate(String key) { - if (FMLCommonHandler.instance().getMinecraftServerInstance().isSinglePlayer()) { - return new TextComponentTranslation(key); - } - return new TextComponentString(net.minecraft.util.text.translation.I18n.translateToLocal(key)); - } - - public static ITextComponent format(String key, Object... args) { - if (FMLCommonHandler.instance().getMinecraftServerInstance().isSinglePlayer()) { - return new TextComponentTranslation(key, args); - } - return new TextComponentString(I18n.translateToLocalFormatted(key, args)); - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/bvh/Bounds2d.java b/src/main/java/net/buildtheearth/terraplusplus/util/bvh/Bounds2d.java index f5488c9b..a0c2dece 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/bvh/Bounds2d.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/bvh/Bounds2d.java @@ -5,6 +5,7 @@ import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.CornerBoundingBox2d; +import net.buildtheearth.terraplusplus.util.TilePos; import net.minecraft.util.math.ChunkPos; import static java.lang.Math.*; @@ -66,7 +67,9 @@ default boolean contains(@NonNull Bounds2d other) { * * @param size the side length of a tile * @return the positions of every tile that intersects this bounding box + * @deprecated use {@link #toTiles(double, int)} */ + @Deprecated default ChunkPos[] toTiles(double size) { double invSize = 1.0d / size; int minXi = floorI(this.minX() * invSize); @@ -83,6 +86,29 @@ default ChunkPos[] toTiles(double size) { return out; } + /** + * Assuming this bounding box is located on a grid of square tiles, gets the positions of every tile that intersects this bounding box. + * + * @param size the side length of a tile + * @param zoom the current zoom level + * @return the positions of every tile that intersects this bounding box + */ + default TilePos[] toTiles(double size, int zoom) { + double invSize = 1.0d / (size * (1 << zoom)); + int minXi = floorI(this.minX() * invSize); + int maxXi = ceilI(this.maxX() * invSize); + int minZi = floorI(this.minZ() * invSize); + int maxZi = ceilI(this.maxZ() * invSize); + + TilePos[] out = new TilePos[(maxXi - minXi + 1) * (maxZi - minZi + 1)]; + for (int i = 0, x = minXi; x <= maxXi; x++) { + for (int z = minZi; z <= maxZi; z++) { + out[i++] = new TilePos(x, z, zoom); + } + } + return out; + } + /** * Returns a bounding box that contains this bounding box and the given one. * diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/bvh/QuadtreeBVH.java b/src/main/java/net/buildtheearth/terraplusplus/util/bvh/QuadtreeBVH.java index ee4e8510..b66808d6 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/bvh/QuadtreeBVH.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/bvh/QuadtreeBVH.java @@ -3,8 +3,8 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; -import net.daporkchop.lib.common.ref.Ref; -import net.daporkchop.lib.common.ref.ThreadRef; +import net.daporkchop.lib.common.reference.ReferenceStrength; +import net.daporkchop.lib.common.reference.cache.Cached; import java.lang.reflect.Array; import java.util.ArrayDeque; @@ -35,7 +35,7 @@ final class QuadtreeBVH implements BVH { */ protected static final double MIN_LEAF_SIZE = 16.0d; - protected static final Ref>> STACK_CACHE = ThreadRef.soft(ArrayDeque::new); + protected static final Cached>> STACK_CACHE = Cached.threadLocal(ArrayDeque::new, ReferenceStrength.SOFT); @Getter(AccessLevel.NONE) protected final Node root; diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/compat/fp2/TerraFP2CompatManager.java b/src/main/java/net/buildtheearth/terraplusplus/util/compat/fp2/TerraFP2CompatManager.java new file mode 100644 index 00000000..be1e3e6b --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/compat/fp2/TerraFP2CompatManager.java @@ -0,0 +1,20 @@ +package net.buildtheearth.terraplusplus.util.compat.fp2; + +import io.github.opencubicchunks.cubicchunks.api.world.ICubicWorldServer; +import lombok.experimental.UtilityClass; +import net.buildtheearth.terraplusplus.generator.EarthGenerator; +//import net.daporkchop.fp2.mode.heightmap.event.RegisterRoughHeightmapGeneratorsEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** + * @author DaPorkchop_ + */ +@UtilityClass +public class TerraFP2CompatManager { + /*@SubscribeEvent + public void registerHeightmapRoughGenerator(RegisterRoughHeightmapGeneratorsEvent event) { + event.registry().addLast("terra++", world -> world instanceof ICubicWorldServer && ((ICubicWorldServer) world).getCubeGenerator() instanceof EarthGenerator + ? new TerraHeightmapGeneratorRough(world) + : null); + }*/ +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/compat/fp2/TerraHeightmapGeneratorRough.java b/src/main/java/net/buildtheearth/terraplusplus/util/compat/fp2/TerraHeightmapGeneratorRough.java new file mode 100644 index 00000000..78274caa --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/compat/fp2/TerraHeightmapGeneratorRough.java @@ -0,0 +1,106 @@ +package net.buildtheearth.terraplusplus.util.compat.fp2; + +import io.github.opencubicchunks.cubicchunks.api.world.ICubicWorldServer; +import io.github.opencubicchunks.cubicchunks.cubicgen.common.biome.IBiomeBlockReplacer; +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; +import net.buildtheearth.terraplusplus.generator.CliffReplacer; +import net.buildtheearth.terraplusplus.generator.EarthGenerator; +import net.buildtheearth.terraplusplus.util.TilePos; +/*import net.daporkchop.fp2.compat.vanilla.FastRegistry; +import net.daporkchop.fp2.mode.heightmap.HeightmapData; +import net.daporkchop.fp2.mode.heightmap.HeightmapPos; +import net.daporkchop.fp2.mode.heightmap.HeightmapTile; +import net.daporkchop.fp2.mode.heightmap.server.gen.rough.AbstractRoughHeightmapGenerator;*/ +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.world.WorldServer; + +import java.util.function.Supplier; + +//import static net.daporkchop.fp2.mode.heightmap.HeightmapConstants.*; +import static net.daporkchop.lib.common.math.PMath.*; + +/** + * @author DaPorkchop_ + */ +/*public class TerraHeightmapGeneratorRough extends AbstractRoughHeightmapGenerator { + protected final EarthGenerator generator; + + public TerraHeightmapGeneratorRough(@NonNull WorldServer world) { + super(world); + + this.generator = (EarthGenerator) ((ICubicWorldServer) world).getCubeGenerator(); + } + + @Override + public boolean supportsLowResolution() { + return true; + } + + @Override + public void generate(@NonNull HeightmapPos pos, @NonNull HeightmapTile tile) { + Supplier fill = this.generator.settings.terrainSettings().fill(); + Supplier water = this.generator.settings.terrainSettings().water(); + Supplier surface = this.generator.settings.terrainSettings().surface(); + Supplier top = this.generator.settings.terrainSettings().top(); + + CachedChunkData cached = this.generator.cache.getUnchecked(new TilePos(pos.x(), pos.z(), pos.level())).join(); + + HeightmapData data = new HeightmapData(); + + HeightmapData waterData = new HeightmapData(); + waterData.light = 15 << 4; + waterData.height_int = this.generator.settings.customCubic().waterLevel - 1; + waterData.height_frac = 224; //256 * 7/8 + waterData.secondaryConnection = 1; + waterData.state = water.get(); + + int blockX = pos.blockX(); + int blockZ = pos.blockZ(); + int level = pos.level(); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int biome = cached.biome(x, z) & 0xFF; + data.biome = waterData.biome = FastRegistry.getBiome(biome); + + int groundHeight = data.height_int = cached.groundHeight(x, z); + int waterHeight = cached.waterHeight(x, z); + waterData.height_int = (groundHeight <= waterHeight ? waterHeight : waterHeight - (1 << level)) - 1; + + //horizontal density change is calculated using the top height rather than the ground height + int topHeight = cached.surfaceHeight(x, z); + double dx = x == 15 ? topHeight - cached.surfaceHeight(x - 1, z) : cached.surfaceHeight(x + 1, z) - topHeight; + double dz = z == 15 ? topHeight - cached.surfaceHeight(x, z - 1) : cached.surfaceHeight(x, z + 1) - topHeight; + + IBlockState state = cached.surfaceBlock(x, z); + if (state != null) { + data.height_int = topHeight; + } else { + if (this.generator.settings.terrainSettings().useCwgReplacers()) { + state = fill.get(); + for (IBiomeBlockReplacer replacer : this.generator.biomeBlockReplacers[biome]) { + state = replacer.getReplacedBlock(state, blockX + (x << level), groundHeight, blockZ + (z << level), dx, -1.0d, dz, 0.0d); + } + + //calling this explicitly increases the likelihood of JIT inlining it + //(for reference: previously, CliffReplacer was manually added to each biome as the last replacer) + //state = CliffReplacer.INSTANCE.getReplacedBlock(state, blockX + (x << level), groundHeight, blockZ + (z << level), dx, -1.0d, dz, 1.0d); + + if (groundHeight < waterHeight && state == top.get()) { //hacky workaround for underwater grass + state = surface.get(); + } + } else { + state = top.get(); + } + } + data.state = state; + + data.light = (15 - clamp(waterHeight - groundHeight, 0, 5) * 3) << 4; + + tile.setLayer(x, z, DEFAULT_LAYER, data); + } + } + } +}*/ diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/compat/sis/SISHelper.java b/src/main/java/net/buildtheearth/terraplusplus/util/compat/sis/SISHelper.java new file mode 100644 index 00000000..f58afb0c --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/compat/sis/SISHelper.java @@ -0,0 +1,277 @@ +package net.buildtheearth.terraplusplus.util.compat.sis; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; +import net.daporkchop.lib.common.annotation.param.NotNegative; +import net.daporkchop.lib.common.misc.threadlocal.TL; +import net.daporkchop.lib.common.pool.array.ArrayAllocator; +import org.apache.sis.geometry.Envelopes; +import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.internal.referencing.ReferencingFactoryContainer; +import org.apache.sis.internal.system.DataDirectory; +import org.apache.sis.metadata.iso.citation.Citations; +import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.referencing.ImmutableIdentifier; +import org.apache.sis.referencing.cs.CoordinateSystems; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.projection.ProjectionException; +import org.apache.sis.referencing.operation.transform.IterationStrategy; +import org.apache.sis.referencing.operation.transform.MathTransforms; +import org.opengis.geometry.Envelope; +import org.opengis.metadata.citation.Citation; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.crs.GeographicCRS; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +@UtilityClass +public class SISHelper { + static { + //suppress 'The “SIS_DATA” environment variable is not set.' warnings in the log + DataDirectory.quiet(); + } + + private static final GeographicCRS TPP_GEO_CRS = CommonCRS.WGS84.normalizedGeographic(); + + /** + * The {@link CoordinateReferenceSystem} used by Terra++ for geographic coordinates. + */ + public static GeographicCRS tppGeoCrs() { + return TPP_GEO_CRS; + } + + private static final Citation TPP_CITATION = Citations.fromName("Terra++"); + + public static Citation tppCitation() { + return TPP_CITATION; + } + + public static ImmutableIdentifier tppOperationIdentifier(@NonNull String name) { + return new ImmutableIdentifier(TPP_CITATION, "Terra++", ("Terra++ " + name).intern()); + } + + private static final TL FACTORIES = TL.initializedWith(ReferencingFactoryContainer::new); + + public static ReferencingFactoryContainer factories() { + return FACTORIES.get(); + } + + private static final Cache PROJECTION_TO_CRS_CACHE = CacheBuilder.newBuilder() + .weakKeys().weakValues() + .build(); + + @SuppressWarnings("deprecation") + @SneakyThrows(ExecutionException.class) + public static CoordinateReferenceSystem projectedCRS(@NonNull GeographicProjection projection) { + if (projection instanceof GeographicProjection.FastProjectedCRS) { //projectedCRS() is fast, we can bypass the cache + return projection.projectedCRS(); + } + + CoordinateReferenceSystem crs = PROJECTION_TO_CRS_CACHE.getIfPresent(projection); + if (crs != null) { //corresponding CRS instance was already cached + return crs; + } + + //get the CRS instance, store it in the cache and return it. + // because calling projection.projectedCRS() might cause recursive calls to this function, and i'm not sure if guava caches supports reentrant loaders, + // we get the projected CRS outside of the cache and then try to insert it, returning any existing values if another thread beats us to it. + CoordinateReferenceSystem realCrs = Objects.requireNonNull(projection.projectedCRS()); + return PROJECTION_TO_CRS_CACHE.get(projection, () -> realCrs); + } + + @SneakyThrows(FactoryException.class) + public static CoordinateOperation findOperation(CoordinateReferenceSystem source, CoordinateReferenceSystem target) { + return CRS.findOperation(source, target, null); + } + + public static boolean isPossibleOutOfBoundsValue(double value) { + return Double.isInfinite(value) || Double.isNaN(value); + } + + private static boolean isAnyPossibleOutOfBoundsValue(double[] values, int fromIndex, int toIndex) { + for (int i = fromIndex; i < toIndex; i++) { + if (isPossibleOutOfBoundsValue(values[i])) { + return true; + } + } + return false; + } + + public static void transformSinglePointWithOutOfBoundsNaN(@NonNull MathTransform transform, double[] src, int srcOff, double[] dst, int dstOff) { + transformSinglePointWithOutOfBoundsNaN(transform, src, srcOff, dst, dstOff, transform.getTargetDimensions()); + } + + private static void transformSinglePointWithOutOfBoundsNaN(MathTransform transform, double[] src, int srcOff, double[] dst, int dstOff, int dstDim) { + try { + transform.transform(src, srcOff, dst, dstOff, 1); + + if (!isAnyPossibleOutOfBoundsValue(dst, dstOff, dstOff + dstDim)) { + return; + } + } catch (TransformException e) { + //silently swallow exception, we'll fill dst with NaN values instead + } + + Arrays.fill(dst, dstOff, dstOff + dstDim, Double.NaN); + } + + private static boolean rangesOverlap(@NotNegative int off0, @NotNegative int len0, @NotNegative int off1, @NotNegative int len1) { + return off0 < off1 + len1 && off1 < off0 + len0; + } + + public static void transformManyPointsWithOutOfBoundsNaN(@NonNull MathTransform transform, double[] src, int srcOff, double[] dst, int dstOff, int count) { + final int srcDim = transform.getSourceDimensions(); + final int dstDim = transform.getTargetDimensions(); + + ArrayAllocator alloc = null; + double[] tempArray = null; + + //noinspection ArrayEquality + if (src == dst && rangesOverlap(srcOff, count * srcDim, dstOff, count * dstDim)) { + //the source and destination ranges overlap, back up all the source values to a temporary array so we can restore them if one of the transform steps + // fails without setting NaNs + alloc = DOUBLE_ALLOC.get(); + tempArray = alloc.atLeast(count * srcDim); + System.arraycopy(src, srcOff, tempArray, 0, count * srcDim); + } + + TRANSFORM_COMPLETE: + { + try { + //try to transform everything, assuming there won't be any failures + transform.transform(src, srcOff, dst, dstOff, count); + } catch (TransformException e) { + SLOW_FALLBACK: + { + MathTransform lastCompletedTransform = e.getLastCompletedTransform(); + if (lastCompletedTransform == null) { //the transform didn't try to transform every point and set failed ones to NaN, no way to continue without going element-by-element + break SLOW_FALLBACK; + } + + List steps = MathTransforms.getSteps(transform); + int i = steps.indexOf(lastCompletedTransform); //find the index of the last completed step in the list of all steps + checkState(i >= 0, "transform step '%s' isn't present in transform '%s'!", lastCompletedTransform, transform); + checkState(steps.lastIndexOf(lastCompletedTransform) == i, "transform step '%s' is present in transform '%s' more than once!", lastCompletedTransform, transform); + + //try to execute all remaining transform steps + for (i++; i < steps.size(); i++) { + try { + steps.get(i).transform(dst, dstOff, dst, dstOff, count); + } catch (TransformException e1) { + if (e1.getLastCompletedTransform() != steps.get(i)) { //the transform didn't try to transform every point and set failed ones to NaN, no way to continue + break SLOW_FALLBACK; + } + } + } + break TRANSFORM_COMPLETE; + } + + //one of the transforms failed without setting the last completed transform, so the destination array contains unknown data. + // we'll fall back to transforming one element at a time, restoring the original source values from the backup made at the start if necessary + if (tempArray != null) { //restore source from backup + src = tempArray; + srcOff = 0; + } + + //transform one element at a time + processWithIterationStrategy( + (src1, srcOff1, srcDim1, dst1, dstOff1, dstDim1) -> transformSinglePointWithOutOfBoundsNaN(transform, src1, srcOff1, dst1, dstOff1, dstDim1), + src, srcOff, srcDim, dst, dstOff, dstDim, count); + } + } + + if (tempArray != null) { //we allocated a temporary array, so we should release it again + alloc.release(tempArray); + } + } + + private static void processWithIterationStrategy(TransformElementProcessor action, double[] src, int srcOff, int srcDim, double[] dst, int dstOff, int dstDim, int count) { + ArrayAllocator alloc = null; + double[] tempArray = null; + + double[] dstFinal = null; + int dstFinalOff = 0; + + int srcInc = srcDim; + int dstInc = dstDim; + switch (IterationStrategy.suggest(srcOff, srcDim, dstOff, dstDim, count)) { + case ASCENDING: + break; + case DESCENDING: + srcOff += (count - 1) * srcInc; + dstOff += (count - 1) * dstInc; + srcInc = -srcInc; + dstInc = -dstInc; + break; + case BUFFER_SOURCE: //allocate a new buffer and copy the source values into it + alloc = DOUBLE_ALLOC.get(); + tempArray = alloc.atLeast(count * srcInc); + System.arraycopy(src, srcOff, tempArray, 0, count * srcInc); + + src = tempArray; + srcOff = 0; + break; + case BUFFER_TARGET: //allocate a new buffer and configure everything to write the destination values into it once everything else has been processed + alloc = DOUBLE_ALLOC.get(); + tempArray = alloc.atLeast(count * dstInc); + + dstFinal = dst; + dstFinalOff = dstOff; + + dst = tempArray; + dstOff = 0; + break; + } + + for (int i = 0, currSrcOff = srcOff, currDstOff = dstOff; i < count; i++, currSrcOff += srcInc, currDstOff += dstInc) { + action.process(src, currSrcOff, srcDim, dst, currDstOff, dstDim); + } + + if (dstFinal != null) { //copy the values into the real destination array + System.arraycopy(dst, dstOff, dstFinal, dstFinalOff, count * dstInc); + } + + if (tempArray != null) { //a temporary array was used, release it + alloc.release(tempArray); + } + } + + @FunctionalInterface + private interface TransformElementProcessor { + void process(double[] src, int srcOff, int srcDim, double[] dst, int dstOff, int dstDim); + } + + //TODO: i'm fairly certain this'll need special handling for large envelopes on dymaxion-based projections + public static GeneralEnvelope transform(CoordinateOperation operation, Envelope envelope) throws TransformException { + return Envelopes.transform(operation, envelope); + } + + public static Bounds2d toBounds(@NonNull Envelope envelope) { + checkArg(envelope.getDimension() == 2); + return Bounds2d.of(envelope.getMinimum(0), envelope.getMaximum(0), envelope.getMinimum(1), envelope.getMaximum(1)); + } + + public static MatrixSIS getAxisOrderMatrix(@NonNull CoordinateOperation operation) { + return Matrices.createTransform(CoordinateSystems.getAxisDirections(operation.getSourceCRS().getCoordinateSystem()), CoordinateSystems.getAxisDirections(operation.getTargetCRS().getCoordinateSystem())); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/CoordinateParseUtils.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/CoordinateParseUtils.java index 2292ea66..fbd594b7 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/geo/CoordinateParseUtils.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/CoordinateParseUtils.java @@ -44,7 +44,7 @@ private static int coordSign(String direction) { } // 02° 49' 52" N 131° 47' 03" E - public static LatLng parseVerbatimCoordinates(final String coordinates) { + public static GeographicCoordinates parseVerbatimCoordinates(final String coordinates) { if (Strings.isNullOrEmpty(coordinates)) { return null; } @@ -100,24 +100,23 @@ public static LatLng parseVerbatimCoordinates(final String coordinates) { return null; } - private static LatLng validateAndRound(double lat, double lon) { - final double latOrig = lat; - final double lngOrig = lon; + private static GeographicCoordinates validateAndRound(double lat, double lon) { lat = roundTo6decimals(lat); lon = roundTo6decimals(lon); if (Double.compare(lat, 0) == 0 && Double.compare(lon, 0) == 0) { - return new LatLng(0, 0); + return GeographicCoordinates.zero(); } if (inRange(lat, lon)) { - return new LatLng(lat, lon); + return GeographicCoordinates.fromLatLonDegrees(lat, lon); } if (Double.compare(lat, 90) > 0 || Double.compare(lat, -90) < 0) { // try and swap if (inRange(lon, lat)) { - return new LatLng(lat, lon); + //TODO: i'm pretty sure this is wrong, and that the arguments here are in fact supposed to be reversed + return GeographicCoordinates.fromLatLonDegrees(lat, lon); } } @@ -180,6 +179,8 @@ private static double coordFromMatcher(Matcher m, int idx1, String sign) { dmsToDecimal(Double.parseDouble(m.group(idx1)), 0.0, 0.0)); } + //TODO: get rid of all the use of Double in here, it's gross + private static double dmsToDecimal(double degree, Double minutes, Double seconds) { minutes = minutes == null ? 0 : minutes; seconds = seconds == null ? 0 : seconds; @@ -188,7 +189,7 @@ private static double dmsToDecimal(double degree, Double minutes, Double seconds // round to 6 decimals (~1m precision) since no way we're getting anything legitimately more precise private static Double roundTo6decimals(Double x) { - return x == null ? null : Math.round(x * Math.pow(10, 6)) / Math.pow(10, 6); + return x == null ? null : Math.round(x * 1e6) * 1e-6; } private CoordinateParseUtils() { diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/GeographicCoordinates.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/GeographicCoordinates.java new file mode 100644 index 00000000..f865f1a1 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/GeographicCoordinates.java @@ -0,0 +1,204 @@ +package net.buildtheearth.terraplusplus.util.geo; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import net.buildtheearth.terraplusplus.util.InternHelper; +import net.buildtheearth.terraplusplus.util.Internable; + +import static java.lang.Math.*; + + +/** + * A {@code (longitude, latitude)} coordinate pair, representing a position on the surface of an ellipsoid. + * + * @author DaPorkchop_ + * @implNote coordinates are represented in decimal degrees + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public abstract class GeographicCoordinates implements Internable { + private static final GeographicCoordinates ZERO = fromLatLonDegrees(0.0d, 0.0d); + + /** + * @return an instance of {@link GeographicCoordinates} with a latitude and longitude of {@code 0.0d} + */ + public static GeographicCoordinates zero() { + return ZERO; + } + + public static GeographicCoordinates fromLatLonDegrees(double latitude, double longitude) { + return new InDegrees(latitude, longitude); + } + + public static GeographicCoordinates fromLonLatDegrees(double longitude, double latitude) { + return fromLatLonDegrees(latitude, longitude); + } + + public static GeographicCoordinates fromLatLonRadians(double latitude, double longitude) { + return new InDegrees(latitude, longitude); + } + + public static GeographicCoordinates fromLonLatRadians(double longitude, double latitude) { + return fromLatLonRadians(latitude, longitude); + } + + protected final double latitude; + protected final double longitude; + + public abstract double latitudeDegrees(); + + public abstract double longitudeDegrees(); + + public abstract GeographicCoordinates withLatitudeDegrees(double latitudeDegrees); + + public abstract GeographicCoordinates withLongitudeDegrees(double longitudeDegrees); + + public abstract double latitudeRadians(); + + public abstract double longitudeRadians(); + + public abstract GeographicCoordinates withLatitudeRadians(double latitudeRadians); + + public abstract GeographicCoordinates withLongitudeRadians(double longitudeRadians); + + /** + * @return {@code true} if both the latitude and longitude are equal to {@code 0.0d} + */ + public boolean isZero() { + return this.latitude == 0.0d && this.longitude == 0.0d; + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (this.getClass() == obj.getClass()) { + GeographicCoordinates other = (GeographicCoordinates) obj; + return this.latitude == other.latitude && this.longitude == other.longitude; + } else { + return false; + } + } + + @Override + public final int hashCode() { + return Double.hashCode(this.latitude) * 31 + Double.hashCode(this.longitude); + } + + @Override + public abstract String toString(); + + @Override + public GeographicCoordinates intern() { + return InternHelper.intern(this); + } + + private static final class InDegrees extends GeographicCoordinates { + private InDegrees(double latitudeDegrees, double longitudeDegrees) { + super(latitudeDegrees, longitudeDegrees); + } + + @Override + public double latitudeDegrees() { + return this.latitude; + } + + @Override + public double longitudeDegrees() { + return this.longitude; + } + + @Override + public GeographicCoordinates withLatitudeDegrees(double latitudeDegrees) { + return latitudeDegrees != this.latitude + ? new InDegrees(latitudeDegrees, this.longitude) + : this; + } + + @Override + public GeographicCoordinates withLongitudeDegrees(double longitudeDegrees) { + return longitudeDegrees != this.longitude + ? new InDegrees(this.latitude, longitudeDegrees) + : this; + } + + @Override + public double latitudeRadians() { + return toRadians(this.latitude); + } + + @Override + public double longitudeRadians() { + return toRadians(this.longitude); + } + + @Override + public GeographicCoordinates withLatitudeRadians(double latitudeRadians) { + return this.withLatitudeDegrees(toDegrees(latitudeRadians)); + } + + @Override + public GeographicCoordinates withLongitudeRadians(double longitudeRadians) { + return this.withLongitudeDegrees(toDegrees(longitudeRadians)); + } + + @Override + public String toString() { + return "EllipsoidalCoordinates(latitude=" + this.latitude + "°, longitude=" + this.longitude + "°)"; + } + } + + private static final class InRadians extends GeographicCoordinates { + private InRadians(double latitudeRadians, double longitudeRadians) { + super(latitudeRadians, longitudeRadians); + } + + @Override + public double latitudeRadians() { + return this.latitude; + } + + @Override + public double longitudeRadians() { + return this.longitude; + } + + @Override + public GeographicCoordinates withLatitudeRadians(double latitudeRadians) { + return latitudeRadians != this.latitude + ? new InRadians(latitudeRadians, this.longitude) + : this; + } + + @Override + public GeographicCoordinates withLongitudeRadians(double longitudeRadians) { + return longitudeRadians != this.longitude + ? new InRadians(this.latitude, longitudeRadians) + : this; + } + + @Override + public double latitudeDegrees() { + return toDegrees(this.latitude); + } + + @Override + public double longitudeDegrees() { + return toDegrees(this.longitude); + } + + @Override + public GeographicCoordinates withLatitudeDegrees(double latitudeDegrees) { + return this.withLatitudeRadians(toRadians(latitudeDegrees)); + } + + @Override + public GeographicCoordinates withLongitudeDegrees(double longitudeDegrees) { + return this.withLongitudeRadians(toRadians(longitudeDegrees)); + } + + @Override + public String toString() { + return "EllipsoidalCoordinates(latitude=" + this.latitude + " rad, longitude=" + this.longitude + " rad)"; + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/LatLng.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/LatLng.java deleted file mode 100644 index 124d447e..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/util/geo/LatLng.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.buildtheearth.terraplusplus.util.geo; - -public class LatLng { - private final Double lat; - private final Double lng; - - public LatLng(double lat, double lng) { - this.lat = lat; - this.lng = lng; - } - - public LatLng() { - this.lat = null; - this.lng = null; - } - - public Double getLat() { - return this.lat; - } - - public Double getLng() { - return this.lng; - } -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/ProjectedCoordinates2d.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/ProjectedCoordinates2d.java new file mode 100644 index 00000000..a83a9144 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/ProjectedCoordinates2d.java @@ -0,0 +1,37 @@ +package net.buildtheearth.terraplusplus.util.geo; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.With; +import net.buildtheearth.terraplusplus.util.InternHelper; +import net.buildtheearth.terraplusplus.util.Internable; + +/** + * @author DaPorkchop_ + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Data +@With +public final class ProjectedCoordinates2d implements Internable { + private static final ProjectedCoordinates2d ZERO = new ProjectedCoordinates2d(0.0d, 0.0d); + + /** + * @return an instance of {@link ProjectedCoordinates2d} with X and Y values of {@code 0.0d} + */ + public static ProjectedCoordinates2d zero() { + return ZERO; + } + + public static ProjectedCoordinates2d ofXY(double x, double y) { + return new ProjectedCoordinates2d(x, y); + } + + private final double x; + private final double y; + + @Override + public ProjectedCoordinates2d intern() { + return InternHelper.intern(this); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AbstractPointArray.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AbstractPointArray.java new file mode 100644 index 00000000..3278bd57 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AbstractPointArray.java @@ -0,0 +1,26 @@ +package net.buildtheearth.terraplusplus.util.geo.pointarray; + +import lombok.Getter; +import lombok.NonNull; +import net.daporkchop.lib.common.annotation.param.NotNegative; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +@Getter +public abstract class AbstractPointArray implements PointArray { + private final PointArray parent; + @NonNull + private final CoordinateReferenceSystem crs; + + private final int size; + + public AbstractPointArray(PointArray parent, @NonNull CoordinateReferenceSystem crs, @NotNegative int size) { + this.parent = parent; + this.crs = crs; + this.size = notNegative(size, "size"); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AbstractPointArray2D.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AbstractPointArray2D.java new file mode 100644 index 00000000..d4e6ce4b --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AbstractPointArray2D.java @@ -0,0 +1,50 @@ +package net.buildtheearth.terraplusplus.util.geo.pointarray; + +import lombok.Getter; +import lombok.NonNull; +import net.daporkchop.lib.common.annotation.param.NotNegative; +import net.daporkchop.lib.common.annotation.param.Positive; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +@Getter +public abstract class AbstractPointArray2D extends AbstractPointArray implements PointArray2D { + public AbstractPointArray2D(PointArray parent, @NonNull CoordinateReferenceSystem crs, @NotNegative int size) { + super(parent, crs, size); + + int crsDimension = crs.getCoordinateSystem().getDimension(); + checkArg(crsDimension == 2, "coordinate system has %d dimensions: %s", crsDimension, crs); + + //make sure that nothing will overflow: + //noinspection ResultOfMethodCallIgnored + Math.multiplyExact(size, 2); + } + + @Override + public final @Positive int pointDimensions() { + return 2; + } + + @Override + public @NotNegative int totalValueSize() { + return Math.multiplyExact(this.size(), 2); + } + + @Override + public int points(@NonNull double[] dst, @NotNegative int dstOff) { + int totalValues = this.totalValueSize(); + checkRangeLen(dst.length, dstOff, totalValues); + + double[] buf = new double[2]; + for (int writerIndex = dstOff, i = 0, size = this.size(); i < size; i++, writerIndex += 2) { + this.point(i, buf); + System.arraycopy(buf, 0, dst, writerIndex, 2); + } + + return totalValues; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AxisAlignedGridPointArray2D.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AxisAlignedGridPointArray2D.java new file mode 100644 index 00000000..faad3fbc --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/AxisAlignedGridPointArray2D.java @@ -0,0 +1,111 @@ +package net.buildtheearth.terraplusplus.util.geo.pointarray; + +import lombok.NonNull; +import lombok.SneakyThrows; +import net.daporkchop.lib.common.annotation.param.NotNegative; +import net.daporkchop.lib.common.annotation.param.Positive; +import org.apache.sis.geometry.Envelope2D; +import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.operation.transform.LinearTransform; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.util.FactoryException; + +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +public final class AxisAlignedGridPointArray2D extends AbstractPointArray2D { + private final int sizeX; + private final int sizeY; + + private final double x; + private final double y; + private final double w; + private final double h; + + private final double indexScaleX; + private final double indexScaleY; + + public AxisAlignedGridPointArray2D(PointArray parent, @NonNull CoordinateReferenceSystem crs, @Positive int sizeX, @Positive int sizeY, double x, double y, double w, double h) { + super(parent, crs, Math.multiplyExact(sizeX, sizeY)); + + this.sizeX = sizeX; + this.sizeY = sizeY; + + this.x = x; + this.y = y; + this.w = w; + this.h = h; + + this.indexScaleX = w / sizeX; + this.indexScaleY = h / sizeY; + } + + @Override + public double[] point(@NotNegative int index, double[] dst) { + checkIndex(this.size(), index); + + if (dst != null) { + checkArg(dst.length == 2, dst.length); + } else { + dst = new double[2]; + } + + int x = index / this.sizeY; + int y = index % this.sizeY; + + dst[0] = this.x + x * this.indexScaleX; + dst[1] = this.y + y * this.indexScaleY; + + return dst; + } + + @Override + public int points(@NonNull double[] dst, @NotNegative int dstOff) { + int totalValues = this.totalValueSize(); + checkRangeLen(dst.length, dstOff, totalValues); + + for (int writerIndex = dstOff, x = 0; x < this.sizeX; x++) { + for (int y = 0; y < this.sizeY; y++, writerIndex += 2) { + dst[writerIndex + 0] = this.x + x * this.indexScaleX; + dst[writerIndex + 1] = this.y + y * this.indexScaleY; + } + } + + return totalValues; + } + + @Override + public Envelope2D envelope() { + return new Envelope2D(this.crs(), this.x, this.y, this.w, this.h); + } + + @Override + @SneakyThrows(FactoryException.class) + public PointArray convert(@NonNull CoordinateReferenceSystem crs, double maxError) { + if (this.crs().equals(crs)) { + return this; + } + + //TODO: i don't necessarily want to enforce this + checkArg(crs.getCoordinateSystem().getDimension() == this.pointDimensions()); + + CoordinateOperation operation = CRS.findOperation(this.crs(), crs, null); + MathTransform transform = operation.getMathTransform(); + if (transform.isIdentity()) { + return this; + } else if (transform instanceof LinearTransform) { + //TODO: turn into a corner bounding box + } + + return new TransformedGridPointArray2D(this, crs, operation); + } + + @Override + public double[] estimatedPointDensity() { + return new double[] { this.indexScaleX, this.indexScaleY }; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/PointArray.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/PointArray.java new file mode 100644 index 00000000..60705e5c --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/PointArray.java @@ -0,0 +1,88 @@ +package net.buildtheearth.terraplusplus.util.geo.pointarray; + +import lombok.NonNull; +import net.daporkchop.lib.common.annotation.param.NotNegative; +import net.daporkchop.lib.common.annotation.param.Positive; +import org.opengis.geometry.Envelope; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +/** + * An array of points which can be converted between coordinate systems. + * + * @author DaPorkchop_ + */ +public interface PointArray { + /** + * @return the dimensionality of each point in this array + */ + @Positive int pointDimensions(); + + /** + * @return the length of this array + */ + @NotNegative int size(); + + /** + * @return the total number of coordinate values in this array, equal to the number of points times the {@link #pointDimensions() point dimensionality} + */ + default @NotNegative int totalValueSize() { + return Math.multiplyExact(this.size(), this.pointDimensions()); + } + + /** + * Gets the coordinates of the point at the given index. + * + * @param index the index of the point to get + * @param dst a {@code double[]} to store the resulting point coordinates in. May be {@code null}, in which case a new array will be allocated and returned. + * @return the coordinates of the point at the given index + * @throws IndexOutOfBoundsException if the given index exceeds this array's size + * @throws IllegalArgumentException if the given destination array is non-{@code null} and its length is not equal to this array's {@link #pointDimensions() point dimensionality} + */ + double[] point(@NotNegative int index, double[] dst); + + /** + * Gets the coordinates of every pont in this array. + *

+ * Points are written tightly packed into the given destination array in index order. + * + * @param dst a {@code double[]} to store the resulting point coordinates in + * @param dstOff the starting index to begin writing into the given destination array + * @return the number of {@code double} values written into the given array, equal to {@link #totalValueSize()} + * @throws IndexOutOfBoundsException if the given destination array has insufficient capacity for the coordinate values (i.e. less than {@link #totalValueSize()}) + */ + int points(@NonNull double[] dst, @NotNegative int dstOff); + + /** + * @return the {@link CoordinateReferenceSystem CRS} in which the array's points are represented + */ + CoordinateReferenceSystem crs(); + + /** + * @return an {@link Envelope} which contains all points in this array + */ + Envelope envelope(); + + /** + * @return the point array from which this point array is derived, or {@code null} if this is not a derived point array + */ + PointArray parent(); + + /** + * Converts the points in this array to the given {@link CoordinateReferenceSystem coordinate system}. + *

+ * Any points in this array which are unable to converted to the given coordinate system will have all their coordinates set to {@link Double#NaN}, and will + * remain invalid even in the case of additional subsequent conversions. + * + * @param crs the {@link CoordinateReferenceSystem coordinate system} to transform this array's points into + * @param maxError indicates the maximum permitted error (the distance between the points in the returned {@link PointArray} and the point's actual positions + * if they were transformed individually to the target coordinate system). This may be used to permit an implementation to optimize a + * transformation at the cost of some accuracy, while setting an upper bound on the potential loss of precision. + * @return a {@link PointArray} containing this array's points after conversion to the given coordinate system + */ + PointArray convert(@NonNull CoordinateReferenceSystem crs, double maxError); + + /** + * @return a {@code double[]} with each element indicating the estimated expected number of units between samples along the corresponding axis + */ + double[] estimatedPointDensity(); +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/PointArray2D.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/PointArray2D.java new file mode 100644 index 00000000..3bf030bd --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/PointArray2D.java @@ -0,0 +1,25 @@ +package net.buildtheearth.terraplusplus.util.geo.pointarray; + +import net.daporkchop.lib.common.annotation.param.NotNegative; +import net.daporkchop.lib.common.annotation.param.Positive; +import org.apache.sis.geometry.Envelope2D; + +/** + * Specialization of {@link PointArray} for arrays of two-dimensional points. + * + * @author DaPorkchop_ + */ +public interface PointArray2D extends PointArray { + @Override + default @Positive int pointDimensions() { + return 2; + } + + @Override + default @NotNegative int totalValueSize() { + return Math.multiplyExact(this.size(), 2); + } + + @Override + Envelope2D envelope(); +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/TransformedGridPointArray2D.java b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/TransformedGridPointArray2D.java new file mode 100644 index 00000000..9c558b47 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/geo/pointarray/TransformedGridPointArray2D.java @@ -0,0 +1,84 @@ +package net.buildtheearth.terraplusplus.util.geo.pointarray; + +import lombok.NonNull; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.util.compat.sis.SISHelper; +import net.daporkchop.lib.common.annotation.param.NotNegative; +import org.apache.sis.geometry.Envelope2D; +import org.apache.sis.util.ComparisonMode; +import org.apache.sis.util.LenientComparable; +import org.opengis.geometry.Envelope; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; + +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +public final class TransformedGridPointArray2D extends AbstractPointArray2D { + private final CoordinateOperation operation; + private final MathTransform transform; + + public TransformedGridPointArray2D(@NonNull PointArray parent, @NonNull CoordinateReferenceSystem crs) { + this(parent, crs, SISHelper.findOperation(parent.crs(), crs)); + } + + TransformedGridPointArray2D(@NonNull PointArray parent, @NonNull CoordinateReferenceSystem crs, @NonNull CoordinateOperation operation) { + super(parent, crs, parent.size()); + + checkArg(parent.pointDimensions() == this.pointDimensions()); //TODO: maybe don't require this in the future? + + checkArg(((LenientComparable) operation.getSourceCRS()).equals(parent.crs(), ComparisonMode.IGNORE_METADATA), "parent crs=%s, operation source crs=%s", parent.crs(), operation.getSourceCRS()); + checkArg(((LenientComparable) operation.getTargetCRS()).equals(crs, ComparisonMode.IGNORE_METADATA), "this crs=%s, operation target crs=%s", crs, operation.getTargetCRS()); + + this.operation = operation; + this.transform = operation.getMathTransform(); + } + + @Override + public double[] point(@NotNegative int index, double[] dst) { + dst = this.parent().point(index, dst); + SISHelper.transformSinglePointWithOutOfBoundsNaN(this.transform, dst, 0, dst, 0); + return dst; + } + + @Override + public int points(@NonNull double[] dst, @NotNegative int dstOff) { + this.parent().points(dst, dstOff); + SISHelper.transformManyPointsWithOutOfBoundsNaN(this.transform, dst, dstOff, dst, dstOff, this.size()); + return this.totalValueSize(); + } + + @Override + @SneakyThrows(TransformException.class) + public Envelope2D envelope() { + return new Envelope2D(SISHelper.transform(this.operation, this.parent().envelope())); + } + + @Override + public PointArray convert(@NonNull CoordinateReferenceSystem crs, double maxError) { + if (this.crs().equals(crs)) { + return this; + } + + //TODO: this could be optimized quite a bit + return new TransformedGridPointArray2D(this, crs); + } + + @Override + public double[] estimatedPointDensity() { + double[] parentDensity = this.parent().estimatedPointDensity(); + Envelope parentEnvelope = this.parent().envelope(); + Envelope selfEnvelope = this.envelope(); + + //TODO: this assumes axis order is consistent between the two! + + return new double[]{ + parentDensity[0] * selfEnvelope.getSpan(0) / parentEnvelope.getSpan(0), + parentDensity[1] * selfEnvelope.getSpan(1) / parentEnvelope.getSpan(1), + }; + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/http/Disk.java b/src/main/java/net/buildtheearth/terraplusplus/util/http/Disk.java index dfb20278..032bf81e 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/http/Disk.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/http/Disk.java @@ -138,16 +138,6 @@ public Path cacheFileFor(@NonNull String url) { } } - /** - * Gets the path to an additional configuration file with the given name. - * - * @param name the configuration file's name - * @return the path to the configuration file - */ - public Path configFile(@NonNull String name) { - return CACHE_ROOT.resolveSibling("config").resolve(name); - } - private void pruneCache() throws IOException { if (!TerraConfig.reducedConsoleMessages) { TerraMod.LOGGER.info("running cache cleanup..."); diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/http/HostManager.java b/src/main/java/net/buildtheearth/terraplusplus/util/http/HostManager.java index 3ac66e82..b7d83caa 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/http/HostManager.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/http/HostManager.java @@ -27,7 +27,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.ToString; -import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.util.TerraConstants; import net.daporkchop.lib.common.misc.string.PStrings; import net.daporkchop.lib.common.util.PorkUtil; diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/http/Http.java b/src/main/java/net/buildtheearth/terraplusplus/util/http/Http.java index dd5775be..00e8e6db 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/http/Http.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/http/Http.java @@ -26,10 +26,9 @@ import lombok.experimental.UtilityClass; import net.buildtheearth.terraplusplus.TerraConfig; import net.buildtheearth.terraplusplus.TerraMod; -import net.daporkchop.lib.common.function.throwing.EFunction; +import net.daporkchop.lib.common.function.exception.EFunction; import net.daporkchop.lib.common.misc.threadfactory.PThreadFactories; -import net.daporkchop.lib.common.ref.Ref; -import net.daporkchop.lib.common.ref.ThreadRef; +import net.daporkchop.lib.common.reference.cache.Cached; import javax.net.ssl.SSLException; import java.net.MalformedURLException; @@ -46,6 +45,7 @@ import java.util.function.BiConsumer; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; import static net.daporkchop.lib.common.util.PValidation.*; @@ -55,6 +55,7 @@ * @author DaPorkchop_ */ @UtilityClass +//TODO: use some sort of cache to prevent multiple concurrent requests to the same URL (unlikely, but probably still possible in some rare circumstances) public class Http { protected static final long TIMEOUT = 20L; @@ -76,7 +77,7 @@ public class Http { protected final int MAX_CONTENT_LENGTH = Integer.MAX_VALUE; //impossibly large, no requests will actually be this big but whatever - protected static final Ref URL_FORMATTING_MATCHER_CACHE = ThreadRef.regex(Pattern.compile("\\$\\{([a-z0-9.]+)}")); + protected static final Cached URL_FORMATTING_MATCHER_CACHE = Cached.regex(Pattern.compile("\\$\\{([a-z0-9.]+)}")); static { try { @@ -105,7 +106,7 @@ public CompletableFuture get(@NonNull String url) { return future; } - public void get(@NonNull String _url, @NonNull CompletableFuture future) { + private void get(@NonNull String _url, @NonNull CompletableFuture future) { class State implements BiConsumer, HostManager.Callback { URL parsed; Path cacheFile; @@ -391,6 +392,29 @@ public static CompletableFuture getSingle(@NonNull String url, @NonNull E })); } + public static String[] suffixAll(@NonNull String[] urls, @NonNull String suffix) { + for (String url : urls) { + checkArg(url.charAt(url.length() - 1) == '/', "url must end with a '/': %s", url); + } + String[] result = urls.clone(); + for (int i = 0; i < result.length; i++) { + result[i] += suffix; + } + return result; + } + + public static String[] flatten(@NonNull String[] baseUrls, @NonNull String[] outputs) { + return Stream.of(outputs) + .flatMap(output -> { + if (output.matches("^[a-z]+://.+")) { //output is absolute (it has a protocol) + return Stream.of(output); + } else { //output is relative, so duplicate it for every base URL + return Stream.of(baseUrls).map(baseUrl -> baseUrl + output); + } + }) + .toArray(String[]::new); + } + public static String formatUrl(@NonNull Map properties, @NonNull String url) { Matcher matcher = URL_FORMATTING_MATCHER_CACHE.get().reset(url); if (matcher.find()) { diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/jackson/IntListDeserializer.java b/src/main/java/net/buildtheearth/terraplusplus/util/jackson/IntListDeserializer.java new file mode 100644 index 00000000..14335127 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/jackson/IntListDeserializer.java @@ -0,0 +1,43 @@ +package net.buildtheearth.terraplusplus.util.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.json.JsonMapper; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; + +import java.io.IOException; +import java.util.stream.IntStream; +import java.util.stream.StreamSupport; + +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +public class IntListDeserializer extends JsonDeserializer { + @Override + public int[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + JsonMapper mapper = (JsonMapper) p.getCodec(); + JsonNode node = mapper.readTree(p); + + if (node.isNumber()) { + return new int[]{ node.asInt() }; + } else if (node.isArray()) { + return StreamSupport.stream(node.spliterator(), false) + .mapToInt(n -> { + checkArg(n.isNumber(), "not an int: %s", n); + return n.asInt(); + }) + .distinct().sorted().toArray(); + } else if (node.isObject()) { + IntRange range = mapper.treeToValue(node, IntRange.class); + return IntStream.rangeClosed(range.min(), range.max()).toArray(); + } + + throw new IllegalArgumentException(node.toString()); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/IntRange.java b/src/main/java/net/buildtheearth/terraplusplus/util/jackson/IntRange.java similarity index 71% rename from src/main/java/net/buildtheearth/terraplusplus/util/IntRange.java rename to src/main/java/net/buildtheearth/terraplusplus/util/jackson/IntRange.java index 834a51ed..c18229a0 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/IntRange.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/jackson/IntRange.java @@ -1,4 +1,4 @@ -package net.buildtheearth.terraplusplus.util; +package net.buildtheearth.terraplusplus.util.jackson; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -33,4 +33,14 @@ public IntRange( this.min = min; this.max = max; } + + /** + * Checks if this range contains the given value. + * + * @param value the value + * @return {@code true} if this range contains the given value + */ + public boolean contains(int value) { + return value >= this.min && value <= this.max; + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/BiomeDeserializeMixin.java b/src/main/java/net/buildtheearth/terraplusplus/util/jackson/mixin/BiomeMixin.java similarity index 50% rename from src/main/java/net/buildtheearth/terraplusplus/util/BiomeDeserializeMixin.java rename to src/main/java/net/buildtheearth/terraplusplus/util/jackson/mixin/BiomeMixin.java index cc201bca..d7da316d 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/BiomeDeserializeMixin.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/jackson/mixin/BiomeMixin.java @@ -1,6 +1,7 @@ -package net.buildtheearth.terraplusplus.util; +package net.buildtheearth.terraplusplus.util.jackson.mixin; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.util.StdConverter; import lombok.NonNull; import net.minecraft.util.ResourceLocation; @@ -11,9 +12,10 @@ /** * @author DaPorkchop_ */ -@JsonDeserialize(converter = BiomeDeserializeMixin.Converter.class) -public abstract class BiomeDeserializeMixin { - protected static class Converter extends StdConverter { +@JsonDeserialize(converter = BiomeMixin.DeserializeConverter.class) +@JsonSerialize(converter = BiomeMixin.SerializeConverter.class) +public abstract class BiomeMixin { + protected static class DeserializeConverter extends StdConverter { @Override public Biome convert(@NonNull String value) { ResourceLocation id = new ResourceLocation(value); @@ -21,4 +23,11 @@ public Biome convert(@NonNull String value) { return Biome.REGISTRY.getObject(id); } } + + protected static class SerializeConverter extends StdConverter { + @Override + public String convert(Biome value) { + return value.getRegistryName().toString(); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/BlockStateDeserializeMixin.java b/src/main/java/net/buildtheearth/terraplusplus/util/jackson/mixin/BlockStateMixin.java similarity index 53% rename from src/main/java/net/buildtheearth/terraplusplus/util/BlockStateDeserializeMixin.java rename to src/main/java/net/buildtheearth/terraplusplus/util/jackson/mixin/BlockStateMixin.java index eb6898db..77c2cbde 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/BlockStateDeserializeMixin.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/jackson/mixin/BlockStateMixin.java @@ -1,9 +1,19 @@ -package net.buildtheearth.terraplusplus.util; +package net.buildtheearth.terraplusplus.util.jackson.mixin; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.util.StdConverter; +import lombok.Getter; import lombok.NonNull; +import lombok.RequiredArgsConstructor; import net.daporkchop.lib.common.function.PFunctions; import net.daporkchop.lib.common.util.PorkUtil; import net.minecraft.block.Block; @@ -11,6 +21,7 @@ import net.minecraft.block.state.IBlockState; import net.minecraft.util.ResourceLocation; +import java.io.IOException; import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; @@ -20,8 +31,19 @@ /** * @author DaPorkchop_ */ -@JsonDeserialize(builder = BlockStateDeserializeMixin.Builder.class) -public abstract class BlockStateDeserializeMixin { +@JsonDeserialize(builder = BlockStateMixin.Builder.class) +@JsonSerialize(using = BlockStateMixin.Serializer.class) +public abstract class BlockStateMixin { + @RequiredArgsConstructor + @Getter(onMethod_ = { @JsonGetter }) + @JsonSerialize + protected static class OnlyId { + @NonNull + protected final String id; + } + + @Getter(onMethod_ = { @JsonGetter }) + @JsonSerialize protected static class Builder { protected final String id; protected final Map properties; @@ -50,4 +72,18 @@ public IBlockState build() { return state; } } + + protected static class Serializer extends JsonSerializer { + @Override + public void serialize(IBlockState value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + String id = value.getBlock().getRegistryName().toString(); + Object o; + if (value == value.getBlock().getDefaultState()) { + o = new OnlyId(id); + } else { + o = new Builder(id, value.getProperties().entrySet().stream().collect(Collectors.toMap(e -> e.getKey().getName(), e -> e.getValue().toString()))); + } + gen.writeObject(o); + } + } } diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/AbstractMatrixSIS.java b/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/AbstractMatrixSIS.java new file mode 100644 index 00000000..5154d09a --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/AbstractMatrixSIS.java @@ -0,0 +1,101 @@ +package net.buildtheearth.terraplusplus.util.math.matrix; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import net.daporkchop.lib.common.util.PorkUtil; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.matrix.MismatchedMatrixSizeException; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.resources.Errors; +import org.opengis.referencing.operation.Matrix; + +/** + * Copies some methods from {@link MatrixSIS} to make them not be package-private. + * + * @author DaPorkchop_ + */ +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class AbstractMatrixSIS extends MatrixSIS { + private static final long serialVersionUID = -2209509649445570374L; + + /** + * Ensures that the given array is non-null and has the expected length. + * This is a convenience method for subclasses constructors. + * + * @throws IllegalArgumentException if the given array does not have the expected length. + */ + protected static void ensureLengthMatch(int expected, @NonNull double[] elements) throws IllegalArgumentException { + if (elements.length != expected) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, expected, elements.length)); + } + } + + /** + * Ensures that the given matrix has the given dimension. + * This is a convenience method for subclasses. + */ + protected static void ensureSizeMatch(int numRow, int numCol, Matrix matrix) throws MismatchedMatrixSizeException { + int otherRow = matrix.getNumRow(); + int otherCol = matrix.getNumCol(); + if (numRow != otherRow || numCol != otherCol) { + throw new MismatchedMatrixSizeException(Errors.format(Errors.Keys.MismatchedMatrixSize_4, numRow, numCol, otherRow, otherCol)); + } + } + + /** + * Ensures that the number of rows of a given matrix matches the given value. + * This is a convenience method for {@link #multiply(Matrix)} implementations. + * + * @param expected the expected number of rows. + * @param actual the actual number of rows in the matrix to verify. + * @param numCol the number of columns to report in case of errors. This is an arbitrary + * value and have no incidence on the verification performed by this method. + */ + protected static void ensureNumRowMatch(int expected, int actual, int numCol) { + if (actual != expected) { + throw new MismatchedMatrixSizeException(Errors.format(Errors.Keys.MismatchedMatrixSize_4, expected, "⒩", actual, numCol)); + } + } + + /** + * Returns an exception for the given indices. + */ + protected static IndexOutOfBoundsException indexOutOfBounds(int row, int column) { + return new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndicesOutOfBounds_2, row, column)); + } + + /** + * Stores all matrix elements in the given flat array. This method does not verify the array length. + * All subclasses in this {@code org.apache.sis.referencing.operation.matrix} package override this + * method with a more efficient implementation. + * + * @param dest the destination array. May be longer than necessary (this happen when the caller needs to + * append {@link org.apache.sis.internal.util.DoubleDouble#error} values after the elements). + * @see MatrixSIS#getElements(double[]) + */ + @SuppressWarnings("JavadocReference") + public abstract void getElements(final double[] dest); + + /** + * @author DaPorkchop_ + */ + public static abstract class NonSquare extends AbstractMatrixSIS { + private static final long serialVersionUID = -4756653951333372707L; + + @Override + public final boolean isAffine() { + return false; //non-square matrix + } + + @Override + public final boolean isIdentity() { + return false; //non-square matrix + } + + @Override + public final void transpose() { + throw new UnsupportedOperationException(PorkUtil.className(this)); + } + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/Matrix2x3.java b/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/Matrix2x3.java new file mode 100644 index 00000000..5c0ffef7 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/Matrix2x3.java @@ -0,0 +1,237 @@ +package net.buildtheearth.terraplusplus.util.math.matrix; + +import org.apache.sis.internal.util.Numerics; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.Matrix3; +import org.apache.sis.referencing.operation.matrix.MismatchedMatrixSizeException; +import org.opengis.referencing.operation.Matrix; + +/** + * A {@link Matrix} with {@code 2} rows and {@code 3} columns. + * + *

 ┌         ┐
+ * │ {@linkplain #m00} {@linkplain #m01} {@linkplain #m02} │
+ * │ {@linkplain #m10} {@linkplain #m11} {@linkplain #m12} │
+ * └         ┘
+ * + * @author DaPorkchop_ + * @see Matrix2 + * @see Matrix3 + * @see Matrix3x2 + */ +public final class Matrix2x3 extends AbstractMatrixSIS.NonSquare { + private static final long serialVersionUID = 2858973491875593956L; + + public static final int ROWS = 2; + public static final int COLUMNS = 3; + + public double m00; + public double m01; + public double m02; + public double m10; + public double m11; + public double m12; + + /** + * Creates a new matrix filled with only zero values. + * + * @param ignore shall always be {@code false} in current version. + */ + Matrix2x3(boolean ignore) { + } + + /** + * Creates a new matrix initialized to the specified values. + * + * @param m00 the first matrix element in the first row. + * @param m01 the second matrix element in the first row. + * @param m02 the third matrix element in the first row. + * @param m10 the first matrix element in the second row. + * @param m11 the second matrix element in the second row. + * @param m12 the third matrix element in the second row. + */ + public Matrix2x3(double m00, double m01, double m02, + double m10, double m11, double m12) { + this.m00 = m00; + this.m01 = m01; + this.m02 = m02; + this.m10 = m10; + this.m11 = m11; + this.m12 = m12; + } + + /** + * Creates a new matrix initialized to the specified values. + * The length of the given array must be 6 and the values in the same order as the above constructor. + * + * @param elements elements of the matrix. Column indices vary fastest. + * @throws IllegalArgumentException if the given array does not have the expected length. + * @see #setElements(double[]) + * @see Matrices#create(int, int, double[]) + */ + public Matrix2x3(double[] elements) throws IllegalArgumentException { + this.setElements(elements); + } + + /** + * Creates a new matrix initialized to the same value than the specified one. + * The specified matrix size must be {@value #ROWS}×{@value #COLUMNS}. + * This is not verified by this constructor, since it shall be verified by {@link Matrices}. + * + * @param matrix the matrix to copy. + */ + Matrix2x3(Matrix matrix) { + this.m00 = matrix.getElement(0, 0); + this.m01 = matrix.getElement(0, 1); + this.m02 = matrix.getElement(0, 2); + this.m10 = matrix.getElement(1, 0); + this.m11 = matrix.getElement(1, 1); + this.m12 = matrix.getElement(1, 2); + } + + /** + * Creates a new matrix filled with zero values. + * + * @return a new matrix filled with zero values + */ + public static Matrix2x3 createZero() { + return new Matrix2x3(false); + } + + /** + * Casts or copies the given matrix to a {@code Matrix3x2} implementation. If the given {@code matrix} + * is already an instance of {@code Matrix3x2}, then it is returned unchanged. Otherwise this method + * verifies the matrix size, then copies all elements in a new {@code Matrix3x2} object. + * + * @param matrix the matrix to cast or copy, or {@code null}. + * @return the matrix argument if it can be safely casted (including {@code null} argument), + * or a copy of the given matrix otherwise. + * @throws MismatchedMatrixSizeException if the size of the given matrix is not {@value #ROWS}×{@value #COLUMNS}. + */ + public static Matrix2x3 castOrCopy(Matrix matrix) throws MismatchedMatrixSizeException { + if (matrix == null || matrix instanceof Matrix2x3) { + return (Matrix2x3) matrix; + } + ensureSizeMatch(ROWS, COLUMNS, matrix); + return new Matrix2x3(matrix); + } + + @Override + public int getNumRow() { + return ROWS; + } + + @Override + public int getNumCol() { + return COLUMNS; + } + + @Override + public double getElement(int row, int column) { + if (row >= 0 && row < ROWS && column >= 0 && column < COLUMNS) { + switch (row * COLUMNS + column) { + case 0: + return this.m00; + case 1: + return this.m01; + case 2: + return this.m02; + case 3: + return this.m10; + case 4: + return this.m11; + case 5: + return this.m12; + } + } + throw indexOutOfBounds(row, column); + } + + @Override + public void setElement(int row, int column, double value) { + if (row >= 0 && row < ROWS && column >= 0 && column < COLUMNS) { + switch (row * COLUMNS + column) { + case 0: + this.m00 = value; + return; + case 1: + this.m01 = value; + return; + case 2: + this.m02 = value; + return; + case 3: + this.m10 = value; + return; + case 4: + this.m11 = value; + return; + case 5: + this.m12 = value; + return; + } + } + throw indexOutOfBounds(row, column); + } + + @Override + public double[] getElements() { + return new double[]{ + this.m00, this.m01, this.m02, + this.m10, this.m11, this.m12, + }; + } + + @Override + public void getElements(final double[] dest) { + ensureLengthMatch(ROWS * COLUMNS, dest); + dest[0] = this.m00; + dest[1] = this.m01; + dest[2] = this.m02; + dest[3] = this.m10; + dest[4] = this.m11; + dest[5] = this.m12; + } + + @Override + public void setElements(double[] elements) { + ensureLengthMatch(ROWS * COLUMNS, elements); + this.m00 = elements[0]; + this.m01 = elements[1]; + this.m02 = elements[2]; + this.m10 = elements[3]; + this.m11 = elements[4]; + this.m12 = elements[5]; + } + + @Override + public Matrix2x3 clone() { + return (Matrix2x3) super.clone(); + } + + @Override + public boolean equals(Object object) { + if (object != null && object.getClass() == this.getClass()) { + Matrix2x3 that = (Matrix2x3) object; + return Numerics.equals(this.m00, that.m00) + && Numerics.equals(this.m01, that.m01) + && Numerics.equals(this.m02, that.m02) + && Numerics.equals(this.m10, that.m10) + && Numerics.equals(this.m11, that.m11) + && Numerics.equals(this.m12, that.m12); + } + return false; + } + + @Override + public int hashCode() { + return Long.hashCode(serialVersionUID ^ + (((((Double.doubleToLongBits(this.m00) + + 31 * Double.doubleToLongBits(this.m01)) + + 31 * Double.doubleToLongBits(this.m02)) + + 31 * Double.doubleToLongBits(this.m10)) + + 31 * Double.doubleToLongBits(this.m11)) + + 31 * Double.doubleToLongBits(this.m12))); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/Matrix3x2.java b/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/Matrix3x2.java new file mode 100644 index 00000000..cac96d36 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/Matrix3x2.java @@ -0,0 +1,240 @@ +package net.buildtheearth.terraplusplus.util.math.matrix; + +import org.apache.sis.internal.util.Numerics; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.Matrix3; +import org.apache.sis.referencing.operation.matrix.MismatchedMatrixSizeException; +import org.opengis.referencing.operation.Matrix; + +/** + * A {@link Matrix} with {@code 3} rows and {@code 2} columns. + * + *
 ┌         ┐
+ * │ {@linkplain #m00} {@linkplain #m01} │
+ * │ {@linkplain #m10} {@linkplain #m11} │
+ * │ {@linkplain #m20} {@linkplain #m21} │
+ * └         ┘
+ * + * @author DaPorkchop_ + * @see Matrix2 + * @see Matrix3 + * @see Matrix2x3 + */ +public final class Matrix3x2 extends AbstractMatrixSIS.NonSquare { + private static final long serialVersionUID = 2858973491875593956L; + + public static final int ROWS = 3; + public static final int COLUMNS = 2; + + public double m00; + public double m01; + public double m10; + public double m11; + public double m20; + public double m21; + + /** + * Creates a new matrix filled with only zero values. + * + * @param ignore shall always be {@code false} in current version. + */ + Matrix3x2(boolean ignore) { + } + + /** + * Creates a new matrix initialized to the specified values. + * + * @param m00 the first matrix element in the first row. + * @param m01 the second matrix element in the first row. + * @param m10 the first matrix element in the second row. + * @param m11 the second matrix element in the second row. + * @param m20 the first matrix element in the third row. + * @param m21 the second matrix element in the third row. + */ + public Matrix3x2(double m00, double m01, + double m10, double m11, + double m20, double m21) { + this.m00 = m00; + this.m01 = m01; + this.m10 = m10; + this.m11 = m11; + this.m20 = m20; + this.m21 = m21; + } + + /** + * Creates a new matrix initialized to the specified values. + * The length of the given array must be 6 and the values in the same order as the above constructor. + * + * @param elements elements of the matrix. Column indices vary fastest. + * @throws IllegalArgumentException if the given array does not have the expected length. + * @see #setElements(double[]) + * @see Matrices#create(int, int, double[]) + */ + public Matrix3x2(double[] elements) throws IllegalArgumentException { + this.setElements(elements); + } + + /** + * Creates a new matrix initialized to the same value than the specified one. + * The specified matrix size must be {@value #ROWS}×{@value #COLUMNS}. + * This is not verified by this constructor, since it shall be verified by {@link Matrices}. + * + * @param matrix the matrix to copy. + */ + Matrix3x2(Matrix matrix) { + this.m00 = matrix.getElement(0, 0); + this.m01 = matrix.getElement(0, 1); + this.m10 = matrix.getElement(1, 0); + this.m11 = matrix.getElement(1, 1); + this.m20 = matrix.getElement(2, 0); + this.m21 = matrix.getElement(2, 1); + } + + /** + * Creates a new matrix filled with zero values. + * + * @return a new matrix filled with zero values + */ + public static Matrix3x2 createZero() { + return new Matrix3x2(false); + } + + /** + * Casts or copies the given matrix to a {@code Matrix3x2} implementation. If the given {@code matrix} + * is already an instance of {@code Matrix3x2}, then it is returned unchanged. Otherwise this method + * verifies the matrix size, then copies all elements in a new {@code Matrix3x2} object. + * + * @param matrix the matrix to cast or copy, or {@code null}. + * @return the matrix argument if it can be safely casted (including {@code null} argument), + * or a copy of the given matrix otherwise. + * @throws MismatchedMatrixSizeException if the size of the given matrix is not {@value #ROWS}×{@value #COLUMNS}. + */ + public static Matrix3x2 castOrCopy(Matrix matrix) throws MismatchedMatrixSizeException { + if (matrix == null || matrix instanceof Matrix3x2) { + return (Matrix3x2) matrix; + } + ensureSizeMatch(ROWS, COLUMNS, matrix); + return new Matrix3x2(matrix); + } + + @Override + public int getNumRow() { + return ROWS; + } + + @Override + public int getNumCol() { + return COLUMNS; + } + + @Override + public double getElement(int row, int column) { + if (row >= 0 && row < ROWS && column >= 0 && column < COLUMNS) { + switch (row * COLUMNS + column) { + case 0: + return this.m00; + case 1: + return this.m01; + case 2: + return this.m10; + case 3: + return this.m11; + case 4: + return this.m20; + case 5: + return this.m21; + } + } + throw indexOutOfBounds(row, column); + } + + @Override + public void setElement(int row, int column, double value) { + if (row >= 0 && row < ROWS && column >= 0 && column < COLUMNS) { + switch (row * COLUMNS + column) { + case 0: + this.m00 = value; + return; + case 1: + this.m01 = value; + return; + case 2: + this.m10 = value; + return; + case 3: + this.m11 = value; + return; + case 4: + this.m20 = value; + return; + case 5: + this.m21 = value; + return; + } + } + throw indexOutOfBounds(row, column); + } + + @Override + public double[] getElements() { + return new double[]{ + this.m00, this.m01, + this.m10, this.m11, + this.m20, this.m21, + }; + } + + @Override + public void getElements(final double[] dest) { + ensureLengthMatch(ROWS * COLUMNS, dest); + dest[0] = this.m00; + dest[1] = this.m01; + dest[2] = this.m10; + dest[3] = this.m11; + dest[4] = this.m20; + dest[5] = this.m21; + } + + @Override + public void setElements(double[] elements) { + ensureLengthMatch(ROWS * COLUMNS, elements); + this.m00 = elements[0]; + this.m01 = elements[1]; + this.m10 = elements[2]; + this.m11 = elements[3]; + this.m20 = elements[4]; + this.m21 = elements[5]; + } + + @Override + public Matrix3x2 clone() { + return (Matrix3x2) super.clone(); + } + + @Override + public boolean equals(Object object) { + if (object != null && object.getClass() == this.getClass()) { + Matrix3x2 that = (Matrix3x2) object; + return Numerics.equals(this.m00, that.m00) + && Numerics.equals(this.m01, that.m01) + && Numerics.equals(this.m10, that.m10) + && Numerics.equals(this.m11, that.m11) + && Numerics.equals(this.m20, that.m20) + && Numerics.equals(this.m21, that.m21); + } + return false; + } + + @Override + public int hashCode() { + return Long.hashCode(serialVersionUID ^ + (((((Double.doubleToLongBits(this.m00) + + 31 * Double.doubleToLongBits(this.m01)) + + 31 * Double.doubleToLongBits(this.m10)) + + 31 * Double.doubleToLongBits(this.m11)) + + 31 * Double.doubleToLongBits(this.m20)) + + 31 * Double.doubleToLongBits(this.m21))); + } +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/TMatrices.java b/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/TMatrices.java new file mode 100644 index 00000000..17cc4239 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/math/matrix/TMatrices.java @@ -0,0 +1,251 @@ +package net.buildtheearth.terraplusplus.util.math.matrix; + +import lombok.experimental.UtilityClass; +import net.daporkchop.lib.common.annotation.param.Positive; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.Matrix1; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.Matrix3; +import org.apache.sis.referencing.operation.matrix.Matrix4; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.Matrix; + +import javax.vecmath.Vector3d; + +import static net.buildtheearth.terraplusplus.util.TerraUtils.*; + +/** + * Like {@link Matrices}, but better :) + * + * @author DaPorkchop_ + * @see Matrices + */ +@UtilityClass +public class TMatrices { + /** + * Creates a matrix of size {@code numRow} × {@code numCol} filled with zero values. + * This constructor is convenient when the caller wants to initialize the matrix elements himself. + *

+ *

Implementation note: + * For {@code numRow} == {@code numCol} with a value between + * {@value org.apache.sis.referencing.operation.matrix.Matrix1#SIZE} and + * {@value org.apache.sis.referencing.operation.matrix.Matrix4#SIZE} inclusive, the matrix + * is guaranteed to be an instance of one of {@link Matrix1} … {@link Matrix4} subtypes.
+ * + * @param numRow for a math transform, this is the number of {@linkplain MathTransform#getTargetDimensions() target dimensions} + 1. + * @param numCol for a math transform, this is the number of {@linkplain MathTransform#getSourceDimensions() source dimensions} + 1. + * @return a matrix of the given size with only zero values. + * @see Matrices#createZero(int, int) + */ + public static MatrixSIS createZero(@Positive int numRow, @Positive int numCol) { + if (numRow == numCol) { //fast case for square matrices + return Matrices.createZero(numRow, numCol); + } else if (numRow == 2 && numCol == 3) { + return Matrix2x3.createZero(); + } else if (numRow == 3 && numCol == 2) { + return Matrix3x2.createZero(); + } else { + return Matrices.createZero(numRow, numCol); + } + } + + public static MatrixSIS multiplyExact(Matrix m1, Matrix m2) { + return Matrices.multiply(m1, m2); + } + + // multiplyFast overloads + + public static Matrix2 multiplyFast(Matrix2 m1, Matrix2 m2) { + return new Matrix2( + m1.m00 * m2.m00 + m1.m01 * m2.m10, m1.m00 * m2.m01 + m1.m01 * m2.m11, + m1.m10 * m2.m00 + m1.m11 * m2.m10, m1.m10 * m2.m01 + m1.m11 * m2.m11); + } + + public static void multiplyFast(Matrix2 m1, Matrix2 m2, Matrix2 dst) { + //preload all fields into variables to improve optimization and allow in-place calculation + double m1m00 = m1.m00; + double m1m01 = m1.m01; + double m1m10 = m1.m10; + double m1m11 = m1.m11; + + double m2m00 = m2.m00; + double m2m01 = m2.m01; + double m2m10 = m2.m10; + double m2m11 = m2.m11; + + dst.m00 = m1m00 * m2m00 + m1m01 * m2m10; + dst.m01 = m1m00 * m2m01 + m1m01 * m2m11; + dst.m10 = m1m10 * m2m00 + m1m11 * m2m10; + dst.m11 = m1m10 * m2m01 + m1m11 * m2m11; + } + + public static Matrix2 multiplyFast(Matrix2x3 m1, Matrix3x2 m2) { + return new Matrix2( + m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20, m1.m00 * m2.m01 + m1.m01 * m2.m11 + m1.m02 * m2.m21, + m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20, m1.m10 * m2.m01 + m1.m11 * m2.m11 + m1.m12 * m2.m21); + } + + public static void multiplyFast(Matrix2x3 m1, Matrix3x2 m2, Matrix2 dst) { + dst.m00 = m1.m00 * m2.m00 + m1.m01 * m2.m10 + m1.m02 * m2.m20; + dst.m01 = m1.m00 * m2.m01 + m1.m01 * m2.m11 + m1.m02 * m2.m21; + dst.m10 = m1.m10 * m2.m00 + m1.m11 * m2.m10 + m1.m12 * m2.m20; + dst.m11 = m1.m10 * m2.m01 + m1.m11 * m2.m11 + m1.m12 * m2.m21; + } + + public static void multiplyFast(Matrix2 m1, Matrix2x3 m2, Matrix2x3 dst) { + //preload all fields into variables to improve optimization and allow in-place calculation + double m1m00 = m1.m00; + double m1m01 = m1.m01; + double m1m10 = m1.m10; + double m1m11 = m1.m11; + + double m2m00 = m2.m00; + double m2m01 = m2.m01; + double m2m02 = m2.m02; + double m2m10 = m2.m10; + double m2m11 = m2.m11; + double m2m12 = m2.m12; + + dst.m00 = m1m00 * m2m00 + m1m01 * m2m10; + dst.m01 = m1m00 * m2m01 + m1m01 * m2m11; + dst.m02 = m1m00 * m2m02 + m1m01 * m2m12; + dst.m10 = m1m10 * m2m00 + m1m11 * m2m10; + dst.m11 = m1m10 * m2m01 + m1m11 * m2m11; + dst.m12 = m1m10 * m2m02 + m1m11 * m2m12; + } + + public static void multiplyFast(Matrix3 m1, Matrix3x2 m2, Matrix3x2 dst) { + //preload all fields into variables to improve optimization and allow in-place calculation + double m1m00 = m1.m00; + double m1m01 = m1.m01; + double m1m02 = m1.m02; + double m1m10 = m1.m10; + double m1m11 = m1.m11; + double m1m12 = m1.m12; + double m1m20 = m1.m20; + double m1m21 = m1.m21; + double m1m22 = m1.m22; + + double m2m00 = m2.m00; + double m2m01 = m2.m01; + double m2m10 = m2.m10; + double m2m11 = m2.m11; + double m2m20 = m2.m20; + double m2m21 = m2.m21; + + dst.m00 = m1m00 * m2m00 + m1m01 * m2m10 + m1m02 * m2m20; + dst.m01 = m1m00 * m2m01 + m1m01 * m2m11 + m1m02 * m2m21; + dst.m10 = m1m10 * m2m00 + m1m11 * m2m10 + m1m12 * m2m20; + dst.m11 = m1m10 * m2m01 + m1m11 * m2m11 + m1m12 * m2m21; + dst.m20 = m1m20 * m2m00 + m1m21 * m2m10 + m1m22 * m2m20; + dst.m21 = m1m20 * m2m01 + m1m21 * m2m11 + m1m22 * m2m21; + } + + public static void multiplyFast(Matrix3x2 m1, Matrix2 m2, Matrix3x2 dst) { + //preload all fields into variables to improve optimization and allow in-place calculation + double m1m00 = m1.m00; + double m1m01 = m1.m01; + double m1m10 = m1.m10; + double m1m11 = m1.m11; + double m1m20 = m1.m20; + double m1m21 = m1.m21; + + double m2m00 = m2.m00; + double m2m01 = m2.m01; + double m2m10 = m2.m10; + double m2m11 = m2.m11; + + dst.m00 = m1m00 * m2m00 + m1m01 * m2m10; + dst.m01 = m1m00 * m2m01 + m1m01 * m2m11; + dst.m10 = m1m10 * m2m00 + m1m11 * m2m10; + dst.m11 = m1m10 * m2m01 + m1m11 * m2m11; + dst.m20 = m1m20 * m2m00 + m1m21 * m2m10; + dst.m21 = m1m20 * m2m01 + m1m21 * m2m11; + } + + public static void multiplyFast(Matrix3 m1, Vector3d v2, Vector3d dst) { + //preload all fields into variables to improve optimization and allow in-place calculation + multiplyFast(m1, v2.x, v2.y, v2.z, dst); + } + + public static void multiplyFast(Matrix3 m1, double x2, double y2, double z2, Vector3d dst) { + dst.x = m1.m00 * x2 + m1.m01 * y2 + m1.m02 * z2; + dst.y = m1.m10 * x2 + m1.m11 * y2 + m1.m12 * z2; + dst.z = m1.m20 * x2 + m1.m21 * y2 + m1.m22 * z2; + } + + public static void scaleFast(Matrix m, double f, Matrix dst) { + int numRow = m.getNumRow(); + int numCol = m.getNumCol(); + AbstractMatrixSIS.ensureSizeMatch(numRow, numCol, dst); + + for (int row = 0; row < numRow; row++) { + for (int col = 0; col < numCol; col++) { + dst.setElement(row, col, m.getElement(row, col) * f); + } + } + } + + // scaleFast overloads + + public static void scaleFast(Matrix2 m, double f, Matrix2 dst) { + dst.m00 = m.m00 * f; + dst.m01 = m.m01 * f; + dst.m10 = m.m10 * f; + dst.m11 = m.m11 * f; + } + + public static double detFast(Matrix2 m) { + return m.m00 * m.m11 - m.m01 * m.m10; + } + + public static void invertFast(Matrix2 m, Matrix2 dst) { + double det = detFast(m); + double a = m.m00; + double b = m.m01; + double c = m.m10; + double d = m.m11; + dst.m00 = d / det; + dst.m01 = -b / det; + dst.m10 = -c / det; + dst.m11 = a / det; + } + + public static void pseudoInvertFast(Matrix2x3 m, Matrix3x2 dst) { + double a = m.m00; + double b = m.m01; + double c = m.m02; + double d = m.m10; + double e = m.m11; + double f = m.m12; + + // haha yes: + // https://www.wolframalpha.com/input?i=pseudoinverse+of+%28%28a%2C+b%2C+c%29%2C%28d%2C+e%2C+f%29%29 + // Simplify[Simplify[Simplify[Simplify[Simplify[Simplify[ + // { + // { + // ((d Conjugate[a]+e Conjugate[b]+f Conjugate[c]) Conjugate[d])/(-(b d Conjugate[b d])+a e Conjugate[b d]-c d Conjugate[c d]+a f Conjugate[c d]+b d Conjugate[a e]-a e Conjugate[a e]-c e Conjugate[c e]+b f Conjugate[c e]+c d Conjugate[a f]-a f Conjugate[a f]+c e Conjugate[b f]-b f Conjugate[b f])+(Conjugate[a] (d Conjugate[d]+e Conjugate[e]+f Conjugate[f]))/(b d Conjugate[b d]-a e Conjugate[b d]+c d Conjugate[c d]-a f Conjugate[c d]-b d Conjugate[a e]+a e Conjugate[a e]+c e Conjugate[c e]-b f Conjugate[c e]-c d Conjugate[a f]+a f Conjugate[a f]-c e Conjugate[b f]+b f Conjugate[b f]), + // (Conjugate[a] (a Conjugate[d]+b Conjugate[e]+c Conjugate[f]))/(-(b d Conjugate[b d])+a e Conjugate[b d]-c d Conjugate[c d]+a f Conjugate[c d]+b d Conjugate[a e]-a e Conjugate[a e]-c e Conjugate[c e]+b f Conjugate[c e]+c d Conjugate[a f]-a f Conjugate[a f]+c e Conjugate[b f]-b f Conjugate[b f])+((a Conjugate[a]+b Conjugate[b]+c Conjugate[c]) Conjugate[d])/(b d Conjugate[b d]-a e Conjugate[b d]+c d Conjugate[c d]-a f Conjugate[c d]-b d Conjugate[a e]+a e Conjugate[a e]+c e Conjugate[c e]-b f Conjugate[c e]-c d Conjugate[a f]+a f Conjugate[a f]-c e Conjugate[b f]+b f Conjugate[b f]) + // }, + // { + // ((d Conjugate[a]+e Conjugate[b]+f Conjugate[c]) Conjugate[e])/(-(b d Conjugate[b d])+a e Conjugate[b d]-c d Conjugate[c d]+a f Conjugate[c d]+b d Conjugate[a e]-a e Conjugate[a e]-c e Conjugate[c e]+b f Conjugate[c e]+c d Conjugate[a f]-a f Conjugate[a f]+c e Conjugate[b f]-b f Conjugate[b f])+(Conjugate[b] (d Conjugate[d]+e Conjugate[e]+f Conjugate[f]))/(b d Conjugate[b d]-a e Conjugate[b d]+c d Conjugate[c d]-a f Conjugate[c d]-b d Conjugate[a e]+a e Conjugate[a e]+c e Conjugate[c e]-b f Conjugate[c e]-c d Conjugate[a f]+a f Conjugate[a f]-c e Conjugate[b f]+b f Conjugate[b f]), + // (Conjugate[b] (a Conjugate[d]+b Conjugate[e]+c Conjugate[f]))/(-(b d Conjugate[b d])+a e Conjugate[b d]-c d Conjugate[c d]+a f Conjugate[c d]+b d Conjugate[a e]-a e Conjugate[a e]-c e Conjugate[c e]+b f Conjugate[c e]+c d Conjugate[a f]-a f Conjugate[a f]+c e Conjugate[b f]-b f Conjugate[b f])+((a Conjugate[a]+b Conjugate[b]+c Conjugate[c]) Conjugate[e])/(b d Conjugate[b d]-a e Conjugate[b d]+c d Conjugate[c d]-a f Conjugate[c d]-b d Conjugate[a e]+a e Conjugate[a e]+c e Conjugate[c e]-b f Conjugate[c e]-c d Conjugate[a f]+a f Conjugate[a f]-c e Conjugate[b f]+b f Conjugate[b f]) + // }, + // { + // ((d Conjugate[a]+e Conjugate[b]+f Conjugate[c]) Conjugate[f])/(-(b d Conjugate[b d])+a e Conjugate[b d]-c d Conjugate[c d]+a f Conjugate[c d]+b d Conjugate[a e]-a e Conjugate[a e]-c e Conjugate[c e]+b f Conjugate[c e]+c d Conjugate[a f]-a f Conjugate[a f]+c e Conjugate[b f]-b f Conjugate[b f])+(Conjugate[c] (d Conjugate[d]+e Conjugate[e]+f Conjugate[f]))/(b d Conjugate[b d]-a e Conjugate[b d]+c d Conjugate[c d]-a f Conjugate[c d]-b d Conjugate[a e]+a e Conjugate[a e]+c e Conjugate[c e]-b f Conjugate[c e]-c d Conjugate[a f]+a f Conjugate[a f]-c e Conjugate[b f]+b f Conjugate[b f]), + // (Conjugate[c] (a Conjugate[d]+b Conjugate[e]+c Conjugate[f]))/(-(b d Conjugate[b d])+a e Conjugate[b d]-c d Conjugate[c d]+a f Conjugate[c d]+b d Conjugate[a e]-a e Conjugate[a e]-c e Conjugate[c e]+b f Conjugate[c e]+c d Conjugate[a f]-a f Conjugate[a f]+c e Conjugate[b f]-b f Conjugate[b f])+((a Conjugate[a]+b Conjugate[b]+c Conjugate[c]) Conjugate[f])/(b d Conjugate[b d]-a e Conjugate[b d]+c d Conjugate[c d]-a f Conjugate[c d]-b d Conjugate[a e]+a e Conjugate[a e]+c e Conjugate[c e]-b f Conjugate[c e]-c d Conjugate[a f]+a f Conjugate[a f]-c e Conjugate[b f]+b f Conjugate[b f]) + // } + // }, + // Element[a, Reals]],Element[b, Reals]],Element[c, Reals]],Element[d, Reals]],Element[e, Reals]],Element[f, Reals]] + + double factor = 1.0d / (sq(c) * (sq(d) + sq(e)) - 2.0d * a * c * d * f - 2.0d * b * e * (a * d + c * f) + sq(b) * (sq(d) + sq(f)) + sq(a) * (sq(e) + sq(f))); + + dst.m00 = (a * (e * e + f * f) - b * d * e - c * d * f) * factor; + dst.m01 = (c * (c * d - a * f) + b * b * d - a * b * e) * factor; + dst.m10 = (b * (d * d + f * f) - e * a * d - e * c * f) * factor; + dst.m11 = (c * (c * e - b * f) - a * b * d + a * a * e) * factor; + dst.m20 = (c * (d * d + e * e) - a * d * f - b * e * f) * factor; + dst.m21 = (b * (b * f - c * e) - a * c * d + a * a * f) * factor; + } +} diff --git a/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod b/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod new file mode 100644 index 00000000..590c9b36 --- /dev/null +++ b/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod @@ -0,0 +1,6 @@ +net.buildtheearth.terraplusplus.projection.sis.WrappedProjectionOperationMethod + +net.buildtheearth.terraplusplus.projection.dymaxion.BTEDymaxionProjection$OperationMethod +net.buildtheearth.terraplusplus.projection.dymaxion.ConformalDynmaxionProjection$OperationMethod +net.buildtheearth.terraplusplus.projection.dymaxion.DymaxionProjection$OperationMethod +net.buildtheearth.terraplusplus.projection.SinusoidalProjection$OperationMethod diff --git a/src/main/resources/assets/terraplusplus/lang/en_us.lang b/src/main/resources/assets/terraplusplus/lang/en_us.lang index f0f36f81..d964146c 100644 --- a/src/main/resources/assets/terraplusplus/lang/en_us.lang +++ b/src/main/resources/assets/terraplusplus/lang/en_us.lang @@ -34,7 +34,6 @@ terraplusplus.gui.transformation.swap_axes=Swap Axes ## Projection types terraplusplus.gui.projection.centered_mercator=Mercator terraplusplus.gui.projection.web_mercator=Web Mercator -terraplusplus.gui.projection.web_mercator.zoom=Zoom terraplusplus.gui.projection.transverse_mercator=Transverse Mercator terraplusplus.gui.projection.equirectangular=Equirectangular terraplusplus.gui.projection.sinusoidal=Sinusoidal @@ -42,6 +41,10 @@ terraplusplus.gui.projection.equal_earth=Equal Earth terraplusplus.gui.projection.bte_conformal_dymaxion=BuildTheEarth Conformal Dymaxion terraplusplus.gui.projection.dymaxion=Dymaxion (not BTE!) terraplusplus.gui.projection.conformal_dymaxion=Conformal Dymaxion +terraplusplus.gui.projection.wkt=WKT §c(Advanced!)§r +terraplusplus.gui.projection.wkt.standard=WKT Revision +terraplusplus.gui.projection.wkt.standard.WKT2_2015=WKT2:2015 (ISO 19162:2015) +terraplusplus.gui.projection.wkt.crs=WKT String ## command messages terraplusplus.command.tpll.usage=Usage: /tpll [altitude] diff --git a/src/main/resources/assets/terraplusplus/textures/presets/advanced.png b/src/main/resources/assets/terraplusplus/textures/presets/advanced.png index d23d3982..3ac27ccd 100644 Binary files a/src/main/resources/assets/terraplusplus/textures/presets/advanced.png and b/src/main/resources/assets/terraplusplus/textures/presets/advanced.png differ diff --git a/src/main/resources/assets/terraplusplus/textures/presets/bte.png b/src/main/resources/assets/terraplusplus/textures/presets/bte.png index 6734b155..1ad5544d 100644 Binary files a/src/main/resources/assets/terraplusplus/textures/presets/bte.png and b/src/main/resources/assets/terraplusplus/textures/presets/bte.png differ diff --git a/src/main/resources/assets/terraplusplus/textures/presets/default.png b/src/main/resources/assets/terraplusplus/textures/presets/default.png index 00c63316..819dc0ad 100644 Binary files a/src/main/resources/assets/terraplusplus/textures/presets/default.png and b/src/main/resources/assets/terraplusplus/textures/presets/default.png differ diff --git a/src/main/resources/assets/terraplusplus/textures/presets/mars.png b/src/main/resources/assets/terraplusplus/textures/presets/mars.png new file mode 100644 index 00000000..a3c5b9a8 Binary files /dev/null and b/src/main/resources/assets/terraplusplus/textures/presets/mars.png differ diff --git a/src/main/resources/assets/terraplusplus/textures/presets/moon.png b/src/main/resources/assets/terraplusplus/textures/presets/moon.png new file mode 100644 index 00000000..cd192c2a Binary files /dev/null and b/src/main/resources/assets/terraplusplus/textures/presets/moon.png differ diff --git a/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm.json5 b/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm.json5 deleted file mode 100644 index cc9139b8..00000000 --- a/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm.json5 +++ /dev/null @@ -1,543 +0,0 @@ -/* - * DEFAULT TERRA++ OpenStreetMap DATA INTERPRETATION CONFIG - * - * @author DaPorkchop_ - */ - -{ - "line": { - "first": { - //tunnels shouldn't be generated, ever - "condition": { - "match": { - "and": { - "tag": { - "tunnel": null - }, - "not": { - "tag": { - "tunnel": "no" - } - } - } - }, - "emit": { - "nothing": {} - } - }, - //functionally equivalent to old FREEWAY/LIMITEDACCESS behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "motorway", - "trunk" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "floor_div": { - "*": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 3 - }, - "constant": 2 - }, - "constant": 6 // 2 (original) + 4 (trees) - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old INTERCHANGE behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "motorway_link", - "trunk_link" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "floor_div": { - "*": { - "max": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 2 - }, - "constant": 3 - }, - "constant": 2 - }, - "constant": 4 - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old MAIN behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "primary", - "raceway" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "*": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 2 - }, - "constant": 4 - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old MINOR behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "tertiary", - "residential" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 4 - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old SIDE behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "secondary", - "primary_link", - "secondary_link", - "living_street", - "bus_guideway", - "service", - "unclassified" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "floor_div": { - "+": { - "*": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 3 - }, - "constant": 1 - }, - "constant": 2 - }, - "constant": 4 - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old ROAD behavior - "condition": { - "match": { - "tag": { - //matches anything tagged as "highway" that didn't match any previous filters - "highway": null - } - }, - "emit": { - "narrow": { - "draw": { - "block": { - "id": "minecraft:grass_path" - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old BUILDING behavior - "condition": { - "match": { - "tag": { - //matches anything tagged as "building" - "building": null - } - }, - "emit": { - "narrow": { - "draw": { - "block": { - "id": "minecraft:brick_block" - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old STREAM behavior - "condition": { - "match": { - "tag": { - "waterway": "stream" - } - }, - "emit": { - "wide": { - "draw": { - "water": {} - }, - "radius": { - "constant": 1.5 - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old RIVER behavior - "condition": { - "match": { - "tag": { - "waterway": [ - "river", - "canal" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - //offset weight by -4, because "water" actually uses the weight value and it'll be too high otherwise - "weight_add": { - "delegate": { - "water": {} - }, - "value": -4 - } - }, - "value": 3 - } - } - }, - "radius": { - "constant": 9 // 5 (original) + 4 (trees) - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - } - } - }, - "polygon": { - "first": { - //functionally equivalent to old BUILDING behavior - "condition": { - "match": { - "tag": { - //matches anything tagged as "building" - "building": null - } - }, - "emit": { - "all": { - //buildings shouldn't have trees inside of or right next to them - "distance": { - "draw": { - "no_trees": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 4 - }, - //draw building outline normally - "convert": { - "line": { - "narrow": { - "draw": { - "block": { - "id": "minecraft:brick_block" - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - } - } - } - }, - "condition": { - "match": { - "or": { - "tag": { - //matches anything tagged as "water" - "water": null - }, - "tag": { - //matches anything tagged as "natural=water" - "natural": "water" - }, - "tag": { - //matches anything tagged as "waterway=riverbank" - "waterway": "riverbank" - } - } - }, - "emit": { - "distance": { - "draw": { - "water": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 6 - } - } - }, - //special handling for the caspian sea: it's tagged as natural=coastline, but isn't actually at sea level... - "condition": { - "match": { - "and": { - "tag": { - "natural": "coastline" - }, - "intersects": { - "minX": 45.637, - "minZ": 34.597, - "maxX": 56.536, - "maxZ": 47.695 - } - } - }, - "emit": { - "distance": { - "draw": { - "water": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 6 - } - } - }, - //all remaining coastline polygons are treated as ocean - "condition": { - "match": { - "tag": { - //matches anything tagged as "natural=coastline" - "natural": "coastline" - } - }, - "emit": { - "distance": { - "draw": { - "ocean": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 7 - } - } - } - } - } -} diff --git a/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_buildings.json5 b/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_buildings.json5 deleted file mode 100644 index 0b6834df..00000000 --- a/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_buildings.json5 +++ /dev/null @@ -1,477 +0,0 @@ -/* - * DEFAULT TERRA++ OpenStreetMap DATA INTERPRETATION CONFIG - * - * BUILDINGS ARE DISABLED - * - * @author DaPorkchop_ - */ - -{ - "line": { - "first": { - //tunnels shouldn't be generated, ever - "condition": { - "match": { - "and": { - "tag": { - "tunnel": null - }, - "not": { - "tag": { - "tunnel": "no" - } - } - } - }, - "emit": { - "nothing": {} - } - }, - //functionally equivalent to old FREEWAY/LIMITEDACCESS behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "motorway", - "trunk" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "floor_div": { - "*": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 3 - }, - "constant": 2 - }, - "constant": 6 // 2 (original) + 4 (trees) - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old INTERCHANGE behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "motorway_link", - "trunk_link" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "floor_div": { - "*": { - "max": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 2 - }, - "constant": 3 - }, - "constant": 2 - }, - "constant": 4 - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old MAIN behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "primary", - "raceway" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "*": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 2 - }, - "constant": 4 - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old MINOR behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "tertiary", - "residential" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 4 - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old SIDE behavior - "condition": { - "match": { - "tag": { - "highway": [ - //matches tags with either value - "secondary", - "primary_link", - "secondary_link", - "living_street", - "bus_guideway", - "service", - "unclassified" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - "block": { - "id": "minecraft:concrete", - "properties": { - "color": "gray" - } - } - }, - "value": 3 - } - } - }, - "radius": { - "+": { - "floor_div": { - "+": { - "*": { - "tag": { - "key": "lanes", - "fallback": 2 - }, - "constant": 3 - }, - "constant": 1 - }, - "constant": 2 - }, - "constant": 4 - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old ROAD behavior - "condition": { - "match": { - "tag": { - //matches anything tagged as "highway" that didn't match any previous filters - "highway": null - } - }, - "emit": { - "narrow": { - "draw": { - "block": { - "id": "minecraft:grass_path" - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old STREAM behavior - "condition": { - "match": { - "tag": { - "waterway": "stream" - } - }, - "emit": { - "wide": { - "draw": { - "water": {} - }, - "radius": { - "constant": 1.5 - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old RIVER behavior - "condition": { - "match": { - "tag": { - "waterway": [ - "river", - "canal" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - //offset weight by -4, because "water" actually uses the weight value and it'll be too high otherwise - "weight_add": { - "delegate": { - "water": {} - }, - "value": -4 - } - }, - "value": 3 - } - } - }, - "radius": { - "constant": 9 // 5 (original) + 4 (trees) - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - } - } - }, - "polygon": { - "first": { - "condition": { - "match": { - "or": { - "tag": { - //matches anything tagged as "water" - "water": null - }, - "tag": { - //matches anything tagged as "natural=water" - "natural": "water" - }, - "tag": { - //matches anything tagged as "waterway=riverbank" - "waterway": "riverbank" - } - } - }, - "emit": { - "distance": { - "draw": { - "water": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 6 - } - } - }, - //special handling for the caspian sea: it's tagged as natural=coastline, but isn't actually at sea level... - "condition": { - "match": { - "and": { - "tag": { - "natural": "coastline" - }, - "intersects": { - "minX": 45.637, - "minZ": 34.597, - "maxX": 56.536, - "maxZ": 47.695 - } - } - }, - "emit": { - "distance": { - "draw": { - "water": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 6 - } - } - }, - //all remaining coastline polygons are treated as ocean - "condition": { - "match": { - "tag": { - //matches anything tagged as "natural=coastline" - "natural": "coastline" - } - }, - "emit": { - "distance": { - "draw": { - "ocean": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 7 - } - } - } - } - } -} diff --git a/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_roads.json5 b/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_roads.json5 deleted file mode 100644 index b942e90f..00000000 --- a/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_roads.json5 +++ /dev/null @@ -1,236 +0,0 @@ -/* - * DEFAULT TERRA++ OpenStreetMap DATA INTERPRETATION CONFIG - * - * ROADS AND BUILDINGS ARE DISABLED - * - * @author DaPorkchop_ - */ - -{ - "line": { - "first": { - //functionally equivalent to old BUILDING behavior - "condition": { - "match": { - "tag": { - //matches anything tagged as "building" - "building": null - } - }, - "emit": { - "narrow": { - "draw": { - "block": { - "id": "minecraft:brick_block" - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old STREAM behavior - "condition": { - "match": { - "tag": { - "waterway": "stream" - } - }, - "emit": { - "wide": { - "draw": { - "water": {} - }, - "radius": { - "constant": 1.5 - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old RIVER behavior - "condition": { - "match": { - "tag": { - "waterway": [ - "river", - "canal" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - //offset weight by -4, because "water" actually uses the weight value and it'll be too high otherwise - "weight_add": { - "delegate": { - "water": {} - }, - "value": -4 - } - }, - "value": 3 - } - } - }, - "radius": { - "constant": 9 // 5 (original) + 4 (trees) - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - } - } - }, - "polygon": { - "first": { - //functionally equivalent to old BUILDING behavior - "condition": { - "match": { - "tag": { - //matches anything tagged as "building" - "building": null - } - }, - "emit": { - "all": { - //buildings shouldn't have trees inside of or right next to them - "distance": { - "draw": { - "no_trees": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 4 - }, - //draw building outline normally - "convert": { - "line": { - "narrow": { - "draw": { - "block": { - "id": "minecraft:brick_block" - } - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - } - } - } - }, - "condition": { - "match": { - "or": { - "tag": { - //matches anything tagged as "water" - "water": null - }, - "tag": { - //matches anything tagged as "natural=water" - "natural": "water" - }, - "tag": { - //matches anything tagged as "waterway=riverbank" - "waterway": "riverbank" - } - } - }, - "emit": { - "distance": { - "draw": { - "water": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 6 - } - } - }, - //special handling for the caspian sea: it's tagged as natural=coastline, but isn't actually at sea level... - "condition": { - "match": { - "and": { - "tag": { - "natural": "coastline" - }, - "intersects": { - "minX": 45.637, - "minZ": 34.597, - "maxX": 56.536, - "maxZ": 47.695 - } - } - }, - "emit": { - "distance": { - "draw": { - "water": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 6 - } - } - }, - //all remaining coastline polygons are treated as ocean - "condition": { - "match": { - "tag": { - //matches anything tagged as "natural=coastline" - "natural": "coastline" - } - }, - "emit": { - "distance": { - "draw": { - "ocean": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 7 - } - } - } - } - } -} diff --git a/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_roads_or_buildings.json5 b/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_roads_or_buildings.json5 deleted file mode 100644 index eb255de3..00000000 --- a/src/main/resources/net/buildtheearth/terraplusplus/dataset/osm/osm_no_roads_or_buildings.json5 +++ /dev/null @@ -1,168 +0,0 @@ -/* - * DEFAULT TERRA++ OpenStreetMap DATA INTERPRETATION CONFIG - * - * ROADS AND BUILDINGS ARE DISABLED - * - * @author DaPorkchop_ - */ - -{ - "line": { - "first": { - //functionally equivalent to old STREAM behavior - "condition": { - "match": { - "tag": { - "waterway": "stream" - } - }, - "emit": { - "wide": { - "draw": { - "water": {} - }, - "radius": { - "constant": 1.5 - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - }, - //functionally equivalent to old RIVER behavior - "condition": { - "match": { - "tag": { - "waterway": [ - "river", - "canal" - ] - } - }, - "emit": { - "wide": { - "draw": { - "all": { - "no_trees": {}, - "weight_greater_than": { - "delegate": { - //offset weight by -4, because "water" actually uses the weight value and it'll be too high otherwise - "weight_add": { - "delegate": { - "water": {} - }, - "value": -4 - } - }, - "value": 3 - } - } - }, - "radius": { - "constant": 9 // 5 (original) + 4 (trees) - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - } - } - } - } - } - }, - "polygon": { - "first": { - "condition": { - "match": { - "or": { - "tag": { - //matches anything tagged as "water" - "water": null - }, - "tag": { - //matches anything tagged as "natural=water" - "natural": "water" - }, - "tag": { - //matches anything tagged as "waterway=riverbank" - "waterway": "riverbank" - } - } - }, - "emit": { - "distance": { - "draw": { - "water": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 6 - } - } - }, - //special handling for the caspian sea: it's tagged as natural=coastline, but isn't actually at sea level... - "condition": { - "match": { - "and": { - "tag": { - "natural": "coastline" - }, - "intersects": { - "minX": 45.637, - "minZ": 34.597, - "maxX": 56.536, - "maxZ": 47.695 - } - } - }, - "emit": { - "distance": { - "draw": { - "water": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 6 - } - } - }, - //all remaining coastline polygons are treated as ocean - "condition": { - "match": { - "tag": { - //matches anything tagged as "natural=coastline" - "natural": "coastline" - } - }, - "emit": { - "distance": { - "draw": { - "ocean": {} - }, - "layer": { - "tag": { - "key": "layer", - "fallback": 0 - } - }, - "maxDist": 7 - } - } - } - } - } -} diff --git a/src/main/resources/net/buildtheearth/terraplusplus/dataset/scalar/heights.json5 b/src/main/resources/net/buildtheearth/terraplusplus/dataset/scalar/heights.json5 deleted file mode 100644 index cb20b96f..00000000 --- a/src/main/resources/net/buildtheearth/terraplusplus/dataset/scalar/heights.json5 +++ /dev/null @@ -1,142 +0,0 @@ -/* - * DEFAULT TERRA++ ELEVATION DATASETS - */ - -[ - // - // AWS Terrain Tiles - // https://registry.opendata.aws/terrain-tiles/ - // - - //whole world at max resolution - { - "dataset": { - "urls": [ - "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/13/${x}/${z}.png" - ], - "projection": { - "web_mercator": { - "zoom": 13 - } - }, - "resolution": 256, - "blend": "CUBIC", - "parse": { - "parse_png_terrarium": {} - } - }, - "bounds": { - "minX": -180.0, - "maxX": 180.0, - "minZ": -85, - "maxZ": 85 - }, - "zooms": { - "min": 0, - "max": 3 - }, - "priority": 0.0 - }, - //this is a workaround for the fact that the dataset is broken in the ocean at zoom levels above 10. - // see https://github.com/tilezen/joerd/issues/199 - // - // we sample the whole world at resolution 10 (which is not broken), and only use it if < 1. elevations >= 1 will - // be handled by the first entry (which has lower priority, but better resolution) - { - "dataset": { - "urls": [ - "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/10/${x}/${z}.png" - ], - "projection": { - "web_mercator": { - "zoom": 10 - } - }, - "resolution": 256, - "blend": "CUBIC", - "parse": { - "parse_png_terrarium": {} - } - }, - "bounds": { - "minX": -180.0, - "maxX": 180.0, - "minZ": -85, - "maxZ": 85 - }, - "zooms": { - "min": 0, - "max": 3 - }, - "priority": 1.0, - "condition": { - "less_than": 1.0 - } - }, - - // - // Estonia - // https://geoportaal.maaamet.ee/eng/Maps-and-Data/Elevation-data/Download-Elevation-Data-p664.html - // - { - "dataset": { - "urls": [ - "https://cloud.daporkchop.net/gis/elevation/ee/14/${x}/${z}.png" - ], - "projection": { - "web_mercator": { - "zoom": 14 - } - }, - "resolution": 256, - "blend": "CUBIC", - "parse": { - "parse_png_terrarium": {} - } - }, - "bounds": { // https://github.com/azurro/country-bounding-boxes/blob/master/dataset/ee.json - "minX": 21.3826069, - "maxX": 28.2100175, - "minZ": 57.5093124, - "maxZ": 59.9383754 - }, - "zooms": { - "min": 0, - "max": 1 - }, - "priority": 100.0 - }, - - // - // Slovenia - // http://www.evode.gov.si/index.php?id=69 - // - { - "dataset": { - "urls": [ - "https://cloud.daporkchop.net/gis/dem/earth/si/17/${x}/${z}.tiff" - ], - "projection": { - "web_mercator": { - "zoom": 17 - } - }, - "resolution": 256, - "blend": "CUBIC", - "parse": { - "parse_tiff_fp": {} - } - }, - "bounds": { // https://github.com/azurro/country-bounding-boxes/blob/master/dataset/si.json - "minX": 13.3754696, - "maxX": 16.6114561, - "minZ": 45.4214242, - "maxZ": 46.8766816 - }, - "zooms": { - "min": 0, - "max": 1 - }, - "priority": 100.0 - } -] diff --git a/src/main/resources/net/buildtheearth/terraplusplus/dataset/scalar/tree_cover.json5 b/src/main/resources/net/buildtheearth/terraplusplus/dataset/scalar/tree_cover.json5 deleted file mode 100644 index 0d2502bb..00000000 --- a/src/main/resources/net/buildtheearth/terraplusplus/dataset/scalar/tree_cover.json5 +++ /dev/null @@ -1,51 +0,0 @@ -/* - * DEFAULT TERRA++ TREE COVER DATASETS - */ - -[ - //whole world at max resolution - { - "dataset": { - "urls": [ - "https://cloud.daporkchop.net/gis/treecover2000/12/${x}/${z}.tiff" - ], - "projection": { - "scale": { - "delegate": { - "web_mercator": { - "zoom": 12 - } - }, - "x": 1.0, - "y": 1.0 - } - }, - "resolution": 256, - "blend": "CUBIC", - "parse": { - "divide": { - "delegate": { - "from_int": { - "delegate": { - "grayscale_extract": { - "delegate": { - "parse_tiff": {}, - } - } - } - } - }, - "value": 100.0 - } - } - }, - "zooms": 0, - "bounds": { - "minX": -180.0, - "maxX": 180.0, - "minZ": -60, - "maxZ": 80 - }, - "priority": -100.0 - } -] diff --git a/src/main/resources/net/buildtheearth/terraplusplus/generator/mars.json5 b/src/main/resources/net/buildtheearth/terraplusplus/generator/mars.json5 new file mode 100644 index 00000000..7412e768 --- /dev/null +++ b/src/main/resources/net/buildtheearth/terraplusplus/generator/mars.json5 @@ -0,0 +1,95 @@ +{ + "projection": { + "flip_vertical": { + "delegate": { + "scale": { + "delegate": { + "equal_earth": {} + }, + "x": 4268600, + "y": 4268600 + } + } + } + }, + "useDefaultHeights": false, + "customHeights": [ + [ + "https://cloud.daporkchop.net/gis/dem/mars/" + ] + ], + "useDefaultTreeCover": false, + "biomeFilters": [ + { + "type": "constant", + "biome": "minecraft:void" + } + ], + "osmSettings": { + "type": "disable" + }, + "dataBakers": [ + { + "type": "initial_biomes" + }, + { + "type": "heights" + } + ], + "populators": [ + ], + "terrainSettings": { + "water": { + "id": "minecraft:air" + }, + "surface": { + "id": "minecraft:sand", + "properties": { + "variant": "red_sand" + } + }, + "top": { + "id": "minecraft:sand", + "properties": { + "variant": "red_sand" + } + }, + "useCwgReplacers": false + }, + "skipChunkPopulation": [ + "DUNGEON", + "FIRE", + "GLOWSTONE", + "ICE", + "LAKE", + "LAVA", + "NETHER_LAVA", + "NETHER_LAVA2", + "NETHER_MAGMA", + "ANIMALS", + "CUSTOM" + ], + "skipBiomeDecoration": [ + "BIG_SHROOM", + "CACTUS", + "CLAY", + "DEAD_BUSH", + "DESERT_WELL", + "LILYPAD", + "FLOWERS", + "FOSSIL", + "GRASS", + "ICE", + "LAKE_WATER", + "LAKE_LAVA", + "PUMPKIN", + "REED", + "ROCK", + "SAND", + "SAND_PASS2", + "SHROOM", + "TREE", + "CUSTOM" + ], + "version": 2 +} diff --git a/src/main/resources/net/buildtheearth/terraplusplus/generator/moon.json5 b/src/main/resources/net/buildtheearth/terraplusplus/generator/moon.json5 new file mode 100644 index 00000000..877f14b0 --- /dev/null +++ b/src/main/resources/net/buildtheearth/terraplusplus/generator/moon.json5 @@ -0,0 +1,100 @@ +{ + "projection": { + "flip_vertical": { + "delegate": { + "scale": { + "delegate": { + "equal_earth": {} + }, + "x": 2183200, + "y": 2183200 + } + } + } + }, + "useDefaultHeights": false, + "customHeights": [ + [ + "https://cloud.daporkchop.net/gis/dem/moon/" + ] + ], + "useDefaultTreeCover": false, + "biomeFilters": [ + { + "type": "constant", + "biome": "minecraft:void" + } + ], + "osmSettings": { + "type": "disable" + }, + "dataBakers": [ + { + "type": "initial_biomes" + }, + { + "type": "heights" + } + ], + "populators": [ + ], + "terrainSettings": { + "water": { + "id": "minecraft:air" + }, + "surface": { + "id": "minecraft:stone" + }, + "top": [ + { + "id": "minecraft:gravel" + }, + { + "id": "minecraft:stone" + }, + { + "id": "minecraft:stone", + "properties": { + "variant": "andesite" + } + } + ], + "useCwgReplacers": false + }, + "skipChunkPopulation": [ + "DUNGEON", + "FIRE", + "GLOWSTONE", + "ICE", + "LAKE", + "LAVA", + "NETHER_LAVA", + "NETHER_LAVA2", + "NETHER_MAGMA", + "ANIMALS", + "CUSTOM" + ], + "skipBiomeDecoration": [ + "BIG_SHROOM", + "CACTUS", + "CLAY", + "DEAD_BUSH", + "DESERT_WELL", + "LILYPAD", + "FLOWERS", + "FOSSIL", + "GRASS", + "ICE", + "LAKE_WATER", + "LAKE_LAVA", + "PUMPKIN", + "REED", + "ROCK", + "SAND", + "SAND_PASS2", + "SHROOM", + "TREE", + "CUSTOM" + ], + "version": 2 +} diff --git a/src/main/resources/net/buildtheearth/terraplusplus/generator/settings/osm/osm.json5 b/src/main/resources/net/buildtheearth/terraplusplus/generator/settings/osm/osm.json5 new file mode 100644 index 00000000..bf7250b0 --- /dev/null +++ b/src/main/resources/net/buildtheearth/terraplusplus/generator/settings/osm/osm.json5 @@ -0,0 +1,724 @@ +/* + * DEFAULT TERRA++ OpenStreetMap DATA INTERPRETATION CONFIG + * + * @author DaPorkchop_ + */ + +{ + "line": { + "type": "first", + "children": [ + //tunnels shouldn't be generated, ever + { + "type": "condition", + "if": { + "type": "and", + "children": [ + { + "type": "tag", + //tunnel=* + "tunnel": null + }, + { + "type": "not", + "child": { + "type": "tag", + "tunnel": "no" + } + } + ] + }, + "emit": { + //return something in order to break out of the initial "first" block + "type": "nothing" + }, + }, + //functionally equivalent to old FREEWAY/LIMITEDACCESS behavior + { + "type": "condition", + "if": { + "type": "tag", + "highway": [ + "motorway", + "trunk" + ] + }, + "emit": { + "type": "wide", + "draw": { + "type": "all", + "children": [ + { + "type": "no_trees", + }, + { + "type": "weight_greater_than", + "value": 3, + "child": { + "type": "block", + "state": { + "id": "minecraft:concrete", + "properties": { + "color": "gray" + } + } + } + } + ] + }, + "radius": { + "type": "+", + "first": { + "type": "floor_div", + "first": { + "type": "*", + "first": { + "type": "tag", + "key": "lanes", + "fallback": 2 + }, + "second": { + "type": "constant", + "value": 3 + } + }, + "second": { + "type": "constant", + "value": 2 + } + }, + "second": { + "type": "constant", + // 2 (original) + 4 (trees) + "value": 6 + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //functionally equivalent to old INTERCHANGE behavior + { + "type": "condition", + "if": { + "type": "tag", + "highway": [ + "motorway_link", + "trunk_link" + ] + }, + "emit": { + "type": "wide", + "draw": { + "type": "all", + "children": [ + { + "type": "no_trees", + }, + { + "type": "weight_greater_than", + "value": 3, + "child": { + "type": "block", + "state": { + "id": "minecraft:concrete", + "properties": { + "color": "gray" + } + } + } + } + ] + }, + "radius": { + "type": "+", + "first": { + "type": "floor_div", + "first": { + "type": "*", + "first": { + "type": "max", + "first": { + "type": "tag", + "key": "lanes", + "fallback": 2 + }, + "second": { + "type": "constant", + "value": 2 + } + }, + "second": { + "type": "constant", + "value": 3 + } + }, + "second": { + "type": "constant", + "value": 2 + } + }, + "second": { + "type": "constant", + // 0 (original) + 4 (trees) + "value": 4 + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //functionally equivalent to old MAIN behavior + { + "type": "condition", + "if": { + "type": "tag", + "highway": [ + "primary", + "raceway" + ] + }, + "emit": { + "type": "wide", + "draw": { + "type": "all", + "children": [ + { + "type": "no_trees", + }, + { + "type": "weight_greater_than", + "value": 3, + "child": { + "type": "block", + "state": { + "id": "minecraft:concrete", + "properties": { + "color": "gray" + } + } + } + } + ] + }, + "radius": { + "type": "+", + "first": { + "type": "*", + "first": { + "type": "tag", + "key": "lanes", + "fallback": 2 + }, + "second": { + "type": "constant", + "value": 4 + } + }, + "second": { + "type": "constant", + // 0 (original) + 4 (trees) + "value": 4 + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //functionally equivalent to old MINOR behavior + { + "type": "condition", + "if": { + "type": "tag", + "highway": [ + "tertiary", + "residential" + ] + }, + "emit": { + "type": "wide", + "draw": { + "type": "all", + "children": [ + { + "type": "no_trees", + }, + { + "type": "weight_greater_than", + "value": 3, + "child": { + "type": "block", + "state": { + "id": "minecraft:concrete", + "properties": { + "color": "gray" + } + } + } + } + ] + }, + "radius": { + "type": "+", + "first": { + "type": "tag", + "key": "lanes", + "fallback": 2 + }, + "second": { + "type": "constant", + // 0 (original) + 4 (trees) + "value": 4 + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //functionally equivalent to old SIDE behavior + { + "type": "condition", + "if": { + "type": "tag", + "highway": [ + "secondary", + "primary_link", + "secondary_link", + "living_street", + "bus_guideway", + "service", + "unclassified" + ] + }, + "emit": { + "type": "wide", + "draw": { + "type": "all", + "children": [ + { + "type": "no_trees", + }, + { + "type": "weight_greater_than", + "value": 3, + "child": { + "type": "block", + "state": { + "id": "minecraft:concrete", + "properties": { + "color": "gray" + } + } + } + } + ] + }, + "radius": { + "type": "+", + "first": { + "type": "floor_div", + "first": { + "type": "+", + "first": { + "type": "*", + "first": { + "type": "tag", + "key": "lanes", + "fallback": 2 + }, + "second": { + "type": "constant", + "value": 3 + } + }, + "second": { + "type": "constant", + "value": 1 + } + }, + "second": { + "type": "constant", + "value": 2 + } + }, + "second": { + "type": "constant", + // 0 (original) + 4 (trees) + "value": 4 + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //functionally equivalent to old ROAD behavior + { + "type": "condition", + "if": { + "type": "tag", + //matches anything tagged as "highway=*" that didn't match any previous filters + "highway": null + }, + "emit": { + "type": "narrow", + "draw": { + "type": "block", + "state": { + "id": "minecraft:grass_path" + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //functionally equivalent to old BUILDING behavior + { + "type": "condition", + "if": { + "type": "tag", + //matches anything tagged as "building=*" + "building": null + }, + "emit": { + "type": "narrow", + "draw": { + "type": "block", + "state": { + "id": "minecraft:brick_block" + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //functionally equivalent to old STREAM behavior + { + "type": "condition", + "if": { + "type": "tag", + "waterway": "stream" + }, + "emit": { + "type": "wide", + "draw": { + "type": "water" + }, + "radius": { + "type": "constant", + "value": 1.5 + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //functionally equivalent to old RIVER behavior + { + "type": "condition", + "if": { + "type": "tag", + "waterway": [ + "river", + "canal" + ] + }, + "emit": { + "type": "wide", + "draw": { + "type": "all", + "children": [ + { + "type": "no_trees" + }, + { + "type": "weight_greater_than", + "value": 3, + "child": { + //offset weight by -4, because "water" actually uses the weight value and it'll be too high otherwise + "type": "weight_add", + "value": -4, + "child": { + "type": "water" + } + } + } + ] + }, + "radius": { + "type": "constant", + // 5 (original) + 4 (trees) + "value": 9 + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + } + ] + }, + "polygon": { + "type": "first", + "children": [ + //functionally equivalent to old BUILDING behavior + { + "type": "condition", + "if": { + "type": "tag", + //matches anything tagged as "building=*" + "building": null + }, + "emit": { + "type": "all", + "children": [ + //buildings shouldn't have trees inside of or right next to them + { + "type": "rasterize_distance", + "maxDist": 4, + "draw": { + "type": "no_trees" + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + }, + //draw building outline normally (as lines) + { + "type": "convert_to_lines", + "next": { + "type": "narrow", + "draw": { + "type": "block", + "state": { + "id": "minecraft:brick_block" + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + } + ] + } + }, + //fill residential areas with brick + { + "type": "condition", + "if": { + "type": "tag", + "landuse": [ + "commercial", + "education", + "residential", + "industrial", + "retail", + "institutional" + ] + }, + "emit": { + "type": "all", + "children": [ + { + "type": "rasterize_fill", + "draw": { + "type": "conditional_random", + "chance": 0.25, + "child": { + "type": "block", + "state": { + "id": "minecraft:brick_block" + }, + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": -1 + }, + "level": 1 + }, + { + "type": "rasterize_fill", + "draw": { + "type": "conditional_random", + "chance": 0.5, + "child": { + "type": "block", + "state": { + "id": "minecraft:brick_block" + }, + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": -1 + }, + "level": 2 + }, + { + "type": "rasterize_fill", + "draw": { + "type": "block", + "state": { + "id": "minecraft:brick_block" + } + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": -1 + }, + "levels": { + "min": 3, + "max": 1000 + } + } + ] + } + }, + //rasterize river polygons + { + "type": "condition", + "if": { + "type": "or", + "children": [ + { + "type": "tag", + //matches anything tagged as "water=*" + "water": null + }, + { + "type": "tag", + //matches anything tagged as "natural=water" + "natural": "water" + }, + { + "type": "tag", + //matches anything tagged as "waterway=riverbank" + "waterway": "riverbank" + } + ] + }, + "emit": { + "type": "rasterize_distance", + "maxDist": 6, + "draw": { + "type": "water" + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //special handling for the caspian sea: it's tagged as natural=coastline, but isn't actually at sea level... + { + "type": "condition", + "if": { + "type": "and", + "children": [ + { + "type": "tag", + //matches anything tagged as "natural=coastline" + "natural": "coastline" + }, + { + "type": "intersects", + "projection": "EPSG:4326", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 45.637, + 34.597 + ], + [ + 45.637, + 47.695 + ], + [ + 56.536, + 47.695 + ], + [ + 56.536, + 34.597 + ], + [ + 45.637, + 34.597 + ] + ] + ] + } + } + ] + }, + "emit": { + "type": "rasterize_distance", + "maxDist": 6, + "draw": { + "type": "water" + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + }, + //all remaining coastline polygons are treated as ocean + { + "type": "condition", + "if": { + "type": "tag", + //matches anything tagged as "natural=coastline" + "natural": "coastline" + }, + "emit": { + "type": "rasterize_distance", + "maxDist": 7, + "draw": { + "type": "ocean" + }, + "layer": { + "type": "tag", + "key": "layer", + "fallback": 0 + } + } + } + ] + } +} diff --git a/src/main/resources/net/buildtheearth/terraplusplus/projection/epsg/epsg_database_wkt2_2015.properties.lzma b/src/main/resources/net/buildtheearth/terraplusplus/projection/epsg/epsg_database_wkt2_2015.properties.lzma new file mode 100644 index 00000000..c8b16bc4 Binary files /dev/null and b/src/main/resources/net/buildtheearth/terraplusplus/projection/epsg/epsg_database_wkt2_2015.properties.lzma differ diff --git a/src/main/resources/terraplusplus.mixins.json b/src/main/resources/terraplusplus.mixins.json new file mode 100644 index 00000000..f479daf6 --- /dev/null +++ b/src/main/resources/terraplusplus.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "package": "net.buildtheearth.terraplusplus.asm", + "refmap": "terraplusplus.refmap.json", + "compatibilityLevel": "JAVA_8", + "minVersion": "0.7.10", + "mixins": [ + "world.storage.MixinSaveHandler", + "world.storage.MixinWorldInfo" + ], + "client": [ + ], + "server": [ + ] +} diff --git a/src/test/java/JsonSerializationTest.java b/src/test/java/JsonSerializationTest.java new file mode 100644 index 00000000..71246a45 --- /dev/null +++ b/src/test/java/JsonSerializationTest.java @@ -0,0 +1,19 @@ +import com.fasterxml.jackson.core.JsonProcessingException; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.format.TileFormatTiff; +import net.buildtheearth.terraplusplus.dataset.scalar.tile.format.TileTransform; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; + +/** + * @author DaPorkchop_ + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class JsonSerializationTest { + @Test + public void test() throws JsonProcessingException { + System.out.println(JSON_MAPPER.writeValueAsString(new TileFormatTiff(TileFormatTiff.Type.Byte, 0, null, null, TileTransform.NONE))); + } +} diff --git a/src/test/java/geojson/GeoJsonTest.java b/src/test/java/geojson/GeoJsonTest.java new file mode 100644 index 00000000..1e5ca413 --- /dev/null +++ b/src/test/java/geojson/GeoJsonTest.java @@ -0,0 +1,36 @@ +package geojson; + +import net.buildtheearth.terraplusplus.dataset.geojson.GeoJsonObject; +import net.buildtheearth.terraplusplus.dataset.geojson.Geometry; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; +import static net.daporkchop.lib.common.util.PValidation.*; + +/** + * @author DaPorkchop_ + */ +public class GeoJsonTest { + @Test + public void test0() throws IOException { + System.out.println(JSON_MAPPER.readValue("{\"type\":\"LineString\",\"coordinates\":[[1,3],[3.5,25,6]]}", GeoJsonObject.class)); + System.out.println(JSON_MAPPER.readValue("{\"type\":\"LineString\",\"coordinates\":[[1,3],[3.5,25,6]]}", Geometry.class)); + } + + @Test + public void test1() throws IOException { + for (int i = 0; i <= 2; i++) { + try (InputStream in = GeoJsonTest.class.getResourceAsStream(i + ".json")) { + GeoJsonObject o = JSON_MAPPER.readValue(in, GeoJsonObject.class); + System.out.println(o); + String json0 = JSON_MAPPER.writeValueAsString(o); + System.out.println(json0); + String json1 = JSON_MAPPER.writeValueAsString(JSON_MAPPER.readValue(json0, GeoJsonObject.class)); + checkState(json0.equals(json1), "inconsistent values:\n%s\n%s", json0, json1); + } + } + } +} diff --git a/src/test/java/projection/TestSISProjections.java b/src/test/java/projection/TestSISProjections.java new file mode 100644 index 00000000..436ac409 --- /dev/null +++ b/src/test/java/projection/TestSISProjections.java @@ -0,0 +1,945 @@ +package projection; + +import lombok.AllArgsConstructor; +import lombok.NonNull; +import lombok.SneakyThrows; +import net.buildtheearth.terraplusplus.generator.EarthGeneratorSettings; +import net.buildtheearth.terraplusplus.projection.EquirectangularProjection; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.projection.SinusoidalProjection; +import net.buildtheearth.terraplusplus.projection.dymaxion.BTEDymaxionProjection; +import net.buildtheearth.terraplusplus.projection.dymaxion.ConformalDynmaxionProjection; +import net.buildtheearth.terraplusplus.projection.dymaxion.DymaxionProjection; +import net.buildtheearth.terraplusplus.projection.epsg.EPSG3785; +import net.buildtheearth.terraplusplus.projection.epsg.EPSG4326; +import net.buildtheearth.terraplusplus.projection.mercator.CenteredMercatorProjection; +import net.buildtheearth.terraplusplus.projection.mercator.WebMercatorProjection; +import net.buildtheearth.terraplusplus.projection.sis.SISProjectionWrapper; +import net.buildtheearth.terraplusplus.projection.sis.WKTStandard; +import net.buildtheearth.terraplusplus.projection.transform.OffsetProjectionTransform; +import net.buildtheearth.terraplusplus.projection.transform.ScaleProjectionTransform; +import net.buildtheearth.terraplusplus.projection.transform.SwapAxesProjectionTransform; +import net.minecraft.init.Bootstrap; +import org.apache.sis.referencing.operation.matrix.Matrices; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import javax.vecmath.Vector2d; +import java.text.ParseException; +import java.util.Arrays; +import java.util.SplittableRandom; + +import static net.buildtheearth.terraplusplus.util.TerraConstants.*; + +/** + * @author DaPorkchop_ + */ +public class TestSISProjections { + private static final double DEFAULT_D = 1e-14d; + + @BeforeClass + public static void bootstrap() { + Bootstrap.register(); + } + + @AllArgsConstructor + private static class TestProjectionAccuracyConfiguration { + protected final double dForDirect1Direct2; + protected final double dForDirect1SIS2; + protected final double dForSIS1Direct2; + + public TestProjectionAccuracyConfiguration(double d) { + this(d, d, d); + } + + protected PointGenerator getDefaultPointGenerator() { + return new PointGenerator(); + } + + public PointGenerator getPointGeneratorForDirect1Direct2() { + return this.getDefaultPointGenerator(); + } + + public PointGenerator getPointGeneratorForDirect1SIS2() { + return this.getDefaultPointGenerator(); + } + + public PointGenerator getPointGeneratorForSIS1Direct2() { + return this.getDefaultPointGenerator(); + } + + public static class PointGenerator { + private final SplittableRandom rng = this.initRng(); + + protected SplittableRandom initRng() { + return new SplittableRandom(1337); + } + + public void getPoint(int i, Vector2d lonLat) { + lonLat.x = this.rng.nextDouble(-180.0d, 180.0d); + lonLat.y = this.rng.nextDouble(-90.0d, 90.0d); + + this.overrideSpecialPoints(i, lonLat); + } + + protected void overrideSpecialPoints(int i, Vector2d lonLat) { + switch (i) { + case 0: + lonLat.x = -180.0d; + lonLat.y = 0.0d; + break; + case 1: + lonLat.x = 180.0d; + lonLat.y = 0.0d; + break; + case 2: + lonLat.x = 0.0d; + lonLat.y = -90.0d; + break; + case 3: + lonLat.x = 0.0d; + lonLat.y = 90.0d; + break; + } + } + + protected boolean shouldTestFromGeoDerivative(int i, double lon, double lat) { + return true; + } + + protected boolean compareFromGeoDerivative(int i, Matrix2 deriv1, Matrix2 deriv2) { + return veryApproximateEquals(deriv1, deriv2, 0.01d, 1e-1d); + } + + protected boolean shouldTestToGeoDerivative(int i, double lon, double lat) { + return true; + } + + protected boolean compareToGeoDerivative(int i, Matrix2 deriv1, Matrix2 deriv2) { + return veryApproximateEquals(deriv1, deriv2, 0.01d, 1e-1d); + } + } + } + + protected static void testProjectionAccuracy(@NonNull GeographicProjection proj1, @NonNull GeographicProjection proj2) { + testProjectionAccuracy(proj1, proj2, new TestProjectionAccuracyConfiguration(DEFAULT_D)); + } + + protected static void testProjectionAccuracy(@NonNull GeographicProjection proj1, @NonNull GeographicProjection proj2, double d) { + testProjectionAccuracy(proj1, proj2, new TestProjectionAccuracyConfiguration(d)); + } + + protected static void testProjectionAccuracy(@NonNull GeographicProjection proj1, @NonNull GeographicProjection proj2, @NonNull TestProjectionAccuracyConfiguration configuration) { + testProjectionAccuracy0(proj1, proj2, configuration.dForDirect1Direct2, configuration.getPointGeneratorForDirect1Direct2()); + testProjectionAccuracy0(proj1, new SISProjectionWrapper(proj2.projectedCRS()), configuration.dForDirect1SIS2, configuration.getPointGeneratorForDirect1SIS2()); + testProjectionAccuracy0(new SISProjectionWrapper(proj1.projectedCRS()), proj2, configuration.dForSIS1Direct2, configuration.getPointGeneratorForSIS1Direct2()); + } + + protected static void testProjectionAccuracy0(@NonNull GeographicProjection proj1, @NonNull GeographicProjection proj2, double d, @NonNull TestProjectionAccuracyConfiguration.PointGenerator pointGenerator) { + Vector2d lonLat = new Vector2d(); + for (int i = 0; i < 10000; i++) { + try { + pointGenerator.getPoint(i, lonLat); + + double lon = lonLat.x; + double lat = lonLat.y; + + double[] result1; + try { + result1 = proj1.fromGeo(lon, lat); + } catch (OutOfProjectionBoundsException e) { + try { + double[] result2 = proj2.fromGeo(lon, lat); + //TODO: throw new AssertionError("proj1 threw " + e + ", but proj2 returned " + Arrays.toString(result2) + "?!?"); + continue; + } catch (OutOfProjectionBoundsException e1) { + //both projections failed with an exception, which is correct + continue; + } + } + double[] result2 = proj2.fromGeo(lon, lat); + + assert approxEquals(result1, result2, d) + : "fromGeo #" + i + " (" + lat + "°N, " + lon + "°E): " + Arrays.toString(result1) + " != " + Arrays.toString(result2); + + double x = result1[0]; + double y = result1[1]; + + result1 = proj1.toGeo(x, y); + result2 = proj2.toGeo(x, y); + assert approxEquals(result1, result2, d) + : "toGeo #" + i + " (" + lat + "°N, " + lon + "°E) -> (" + x + ", " + y + "): " + Arrays.toString(result1) + " != " + Arrays.toString(result2); + + if (pointGenerator.shouldTestFromGeoDerivative(i, lon, lat)) { + Matrix2 deriv1 = proj1.fromGeoDerivative(lon, lat); + Matrix2 deriv2 = proj2.fromGeoDerivative(lon, lat); + assert pointGenerator.compareFromGeoDerivative(i, deriv1, deriv2) + : "fromGeoDerivative #" + i + " (" + lat + "°N, " + lon + "°E):\n" + deriv1 + "!=\n" + deriv2; + } + + if (pointGenerator.shouldTestToGeoDerivative(i, lon, lat)) { + Matrix2 deriv1 = proj1.toGeoDerivative(x, y); + Matrix2 deriv2 = proj2.toGeoDerivative(x, y); + assert pointGenerator.compareToGeoDerivative(i, deriv1, deriv2) + : "toGeoDerivative #" + i + " (" + lat + "°N, " + lon + "°E) -> (" + x + ", " + y + "):\n" + deriv1 + "!=\n" + deriv2; + } + } catch (OutOfProjectionBoundsException e) { + throw new AssertionError("#" + i, e); + } + } + } + + @Test + @SneakyThrows(ParseException.class) + public void testDymaxion() { + //GeographicProjection projection = new SISProjectionWrapper(WKTStandard.WKT2_2015, "PROJCRS[\"OSGB36 / British National Grid\",BASEGEODCRS[\"OSGB36\",DATUM[\"Ordnance Survey of Great Britain 1936\",ELLIPSOID[\"Airy 1830\",6377563.396,299.3249646,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]]],CONVERSION[\"British National Grid\",METHOD[\"Transverse Mercator\",ID[\"EPSG\",9807]],PARAMETER[\"Latitude of natural origin\",49,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8801]],PARAMETER[\"Longitude of natural origin\",-2,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8802]],PARAMETER[\"Scale factor at natural origin\",0.9996012717,SCALEUNIT[\"unity\",1],ID[\"EPSG\",8805]],PARAMETER[\"False easting\",400000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8806]],PARAMETER[\"False northing\",-100000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8807]]],CS[Cartesian,2],AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"(N)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],SCOPE[\"Engineering survey, topographic mapping.\"],AREA[\"United Kingdom (UK) - offshore to boundary of UKCS within 49°45'N to 61°N and 9°W to 2°E; onshore Great Britain (England, Wales and Scotland). Isle of Man onshore.\"],BBOX[49.75,-9,61.01,2.01],ID[\"EPSG\",27700]]"); + + // PROJCRS["WGS 84 / Terra++ Dymaxion", BASEGEODCRS["WGS 84", DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84", 6378137.0, 298.257223563, UNIT["metre", 1]]], PRIMEM["Greenwich", 0.0, UNIT["degree", 0.017453292519943295]]], CONVERSION["Terra++ Dymaxion", METHOD["Terra++ Internal Projection"], PARAMETER["type", "dymaxion"]], CS[Cartesian, 2], AXIS["Easting (X)", east, ORDER[1]], AXIS["Northing (Y)", north, ORDER[2]], UNIT["metre", 1], SCOPE["Minecraft."], AREA["World."], BBOX[-90.00, -180.00, 90.00, 180.00]] + + testProjectionAccuracy( + new DymaxionProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / Terra++ Dymaxion\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\", 6378137, 298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\", 0,\n" + + " ANGLEUNIT[\"degree\", 0.0174532925199433]],\n" + + " ID[\"EPSG\", 4326]],\n" + + " CONVERSION[\"Terra++ Dymaxion\",\n" + + " METHOD[\"Terra++ Internal Projection\"],\n" + + " PARAMETER[\"type\", \"dymaxion\"]],\n" + + " CS[Cartesian, 2],\n" + + " AXIS[\"X\", east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " AXIS[\"Y\", north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " SCOPE[\"Minecraft.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90, -180, 90, 180]]"), + new TestProjectionAccuracyConfiguration(1e-13d) { + @Override + public PointGenerator getPointGeneratorForSIS1Direct2() { + return new PointGenerator() { + @Override + protected boolean shouldTestToGeoDerivative(int i, double lon, double lat) { + //special handling required here: toGeoDerivative fails because GeographicProjectionHelper.defaultDerivative() gives very wrong results when longitude values wrap around + return i >= 4; + } + }; + } + }); + } + + @Test + @SneakyThrows(ParseException.class) + public void testConformalDymaxion() { + testProjectionAccuracy( + new ConformalDynmaxionProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / Terra++ Conformal Dymaxion\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\", 6378137, 298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\", 0,\n" + + " ANGLEUNIT[\"degree\", 0.0174532925199433]],\n" + + " ID[\"EPSG\", 4326]],\n" + + " CONVERSION[\"Terra++ Dymaxion\",\n" + + " METHOD[\"Terra++ Internal Projection\"],\n" + + " PARAMETER[\"type\", \"conformal_dymaxion\"]],\n" + + " CS[Cartesian, 2],\n" + + " AXIS[\"X\", east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " AXIS[\"Y\", north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " SCOPE[\"Minecraft.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90, -180, 90, 180]]"), + new TestProjectionAccuracyConfiguration(1e-13d) { + @Override + public PointGenerator getPointGeneratorForSIS1Direct2() { + return new PointGenerator() { + @Override + protected boolean shouldTestToGeoDerivative(int i, double lon, double lat) { + //special handling required here: toGeoDerivative fails because GeographicProjectionHelper.defaultDerivative() gives very wrong results when longitude values wrap around + return i >= 4; + } + }; + } + }); + } + + private static TestProjectionAccuracyConfiguration testBTEConfiguration(double dForDirect1Direct2, double dForDirect1SIS2, double dForSIS1Direct2) { + return new TestProjectionAccuracyConfiguration(dForDirect1Direct2, dForDirect1SIS2, dForSIS1Direct2) { + @Override + protected PointGenerator getDefaultPointGenerator() { + return new PointGenerator() { + @Override + protected boolean shouldTestFromGeoDerivative(int i, double lon, double lat) { + //special handling required here: fromGeoDerivative fails because GeographicProjectionHelper.defaultDerivative() gives very wrong results when longitude values wrap around + return i >= 4 + //fromGeoDerivative fails because GeographicProjectionHelper.defaultDerivative() gives somewhat inaccurate results near +-90° + && Math.abs(90.0d - lat) < 0.01d; + } + + @Override + protected boolean compareFromGeoDerivative(int i, Matrix2 deriv1, Matrix2 deriv2) { + return Matrices.equals(deriv1, deriv2, 0.15d, true) + || Matrices.equals(deriv1, deriv2, 1e-6d, false); + } + + @Override + protected boolean shouldTestToGeoDerivative(int i, double lon, double lat) { + //special handling required here: toGeoDerivative fails because GeographicProjectionHelper.defaultDerivative() gives very wrong results when longitude values wrap around + return i >= 4; + } + }; + } + }; + } + + public interface BTETests {} + + @Test + @Category(BTETests.class) + @SneakyThrows(ParseException.class) + public void testBTE0() { + testProjectionAccuracy( + new BTEDymaxionProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / BuildTheEarth Conformal Dymaxion (Unscaled)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\", 6378137, 298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\", 0,\n" + + " ANGLEUNIT[\"degree\", 0.0174532925199433]],\n" + + " ID[\"EPSG\", 4326]],\n" + + " CONVERSION[\"Terra++ BuildTheEarth Conformal Dymaxion (Unscaled)\",\n" + + " METHOD[\"Terra++ Internal Projection\"],\n" + + " PARAMETER[\"type\", \"bte_conformal_dymaxion\"]],\n" + + " CS[Cartesian, 2],\n" + + " AXIS[\"X\", east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " AXIS[\"Y\", north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " SCOPE[\"Minecraft.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90, -180, 90, 180]]"), + testBTEConfiguration(DEFAULT_D, DEFAULT_D, 1e-12d)); + } + + @Test + @Category(BTETests.class) + @SneakyThrows(ParseException.class) + public void testBTE1() { + testProjectionAccuracy( + EarthGeneratorSettings.parse(EarthGeneratorSettings.BTE_DEFAULT_SETTINGS).projection(), + new ScaleProjectionTransform(new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / BuildTheEarth Conformal Dymaxion (Unscaled)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\", 6378137, 298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\", 0,\n" + + " ANGLEUNIT[\"degree\", 0.0174532925199433]],\n" + + " ID[\"EPSG\", 4326]],\n" + + " CONVERSION[\"Terra++ BuildTheEarth Conformal Dymaxion (Unscaled)\",\n" + + " METHOD[\"Terra++ Internal Projection\"],\n" + + " PARAMETER[\"type\", \"bte_conformal_dymaxion\"]],\n" + + " CS[Cartesian, 2],\n" + + " AXIS[\"X\", east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " AXIS[\"Y\", south,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " SCOPE[\"Minecraft.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90, -180, 90, 180]]"), 7318261.522857145d, 7318261.522857145d), + testBTEConfiguration(DEFAULT_D, 2e-11d, 2e-8d)); + } + + @Test + @Category(BTETests.class) + @SneakyThrows(ParseException.class) + public void testBTE2() { + testProjectionAccuracy( + EarthGeneratorSettings.parse(EarthGeneratorSettings.BTE_DEFAULT_SETTINGS).projection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / BuildTheEarth Conformal Dymaxion (Scaled)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\", 6378137, 298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\", 0,\n" + + " ANGLEUNIT[\"degree\", 0.0174532925199433]],\n" + + " ID[\"EPSG\", 4326]],\n" + + " CONVERSION[\"Terra++ BuildTheEarth Conformal Dymaxion (Scaled)\",\n" + + " METHOD[\"Terra++ Internal Projection\"],\n" + + " PARAMETER[\"type\", \"scale\"]," + + " PARAMETER[\"json_args\", \"{\"\"delegate\"\": {\"\"bte_conformal_dymaxion\"\": {}}, \"\"x\"\": 7318261.522857145, \"\"y\"\": 7318261.522857145}\"]],\n" + + " CS[Cartesian, 2],\n" + + " AXIS[\"X\", east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " AXIS[\"Y\", south,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " SCOPE[\"Minecraft.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90, -180, 90, 180]]"), + testBTEConfiguration(DEFAULT_D, DEFAULT_D, 2e-8d)); + } + + @Test + @Category(BTETests.class) + @SneakyThrows(ParseException.class) + public void testBTE3() { + testProjectionAccuracy( + EarthGeneratorSettings.parse(EarthGeneratorSettings.BTE_DEFAULT_SETTINGS).projection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + //TODO: this would be nicer using DERIVEDPROJCRS from WKT2:2019 + "FITTED_CS[\"WGS 84 / Reversed Axis Order / BuildTheEarth Conformal Dymaxion (Scaled)\",\n" + + " PARAM_MT[\"Affine\",\n" + + " METHOD[\"Affine\", ID[\"EPSG\", 9624]],\n" + + " PARAMETER[\"num_col\", 3],\n" + + " PARAMETER[\"num_row\", 3],\n" + + " PARAMETER[\"elt_0_0\", 1.3664447449393513E-7],\n" + + " PARAMETER[\"elt_0_1\", 0],\n" + + " PARAMETER[\"elt_1_0\", 0],\n" + + " PARAMETER[\"elt_1_1\", 1.3664447449393513E-7]],\n" + + " PROJCRS[\"WGS 84 / Reversed Axis Order / BuildTheEarth Conformal Dymaxion (Unscaled)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"Terra++ BuildTheEarth Conformal Dymaxion (Unscaled)\",\n" + + " METHOD[\"Terra++ Internal Projection\"],\n" + + " PARAMETER[\"type\", \"bte_conformal_dymaxion\"]],\n" + + " CS[Cartesian,2],\n" + + " AXIS[\"X\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " AXIS[\"Y\",south,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " SCOPE[\"Minecraft.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90, -180, 90, 180]]]"), + testBTEConfiguration(1e-8d, 1e-8d, 2e-8d)); //this is significantly less accurate than some of the others!!! + } + + //unfortunately, this has a fair amount of additional floating-point error (off by <= ~1e-10 degrees). maybe that's acceptable? will have to test more... + @Test + @Category(BTETests.class) + @SneakyThrows(ParseException.class) + public void testBTE4() { + testProjectionAccuracy( + EarthGeneratorSettings.parse(EarthGeneratorSettings.BTE_DEFAULT_SETTINGS).projection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / BuildTheEarth Conformal Dymaxion (Scaled)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\", 6378137, 298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\", 0,\n" + + " ANGLEUNIT[\"degree\", 0.0174532925199433]],\n" + + " ID[\"EPSG\", 4326]],\n" + + " CONVERSION[\"Terra++ BuildTheEarth Conformal Dymaxion (Unscaled)\",\n" + + " METHOD[\"Terra++ Internal Projection\"],\n" + + " PARAMETER[\"type\", \"bte_conformal_dymaxion\"]],\n" + + " CS[Cartesian, 2],\n" + + " AXIS[\"X\", east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"Minecraft Block\", 1.3664447449393513E-7]],\n" + + " AXIS[\"Y\", south,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"Minecraft Block\", 1.3664447449393513E-7]],\n" + + " SCOPE[\"Minecraft.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90, -180, 90, 180]]"), + testBTEConfiguration(1e-10d, 1e-10d, 2e-8d)); + } + + @Test(expected = AssertionError.class) //This should fail, as + @SneakyThrows(ParseException.class) + @SuppressWarnings("deprecation") + public void testEPSG3785IsNotActuallyEPSG3785() { + testProjectionAccuracy( + new EPSG3785(), + new OffsetProjectionTransform(new ScaleProjectionTransform(new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"Popular Visualisation CRS / Mercator\",\n" + + " BASEGEODCRS[\"Popular Visualisation CRS\",\n" + + " DATUM[\"Popular Visualisation Datum\",\n" + + " ELLIPSOID[\"Popular Visualisation Sphere\",6378137,0,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"Popular Visualisation Mercator\",\n" + + " METHOD[\"Mercator (1SP) (Spherical)\",\n" + + " ID[\"EPSG\",9841]],\n" + + " PARAMETER[\"Latitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8801]],\n" + + " PARAMETER[\"Longitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8802]],\n" + + " PARAMETER[\"Scale factor at natural origin\",1,\n" + + " SCALEUNIT[\"unity\",1],\n" + + " ID[\"EPSG\",8805]],\n" + + " PARAMETER[\"False easting\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8806]],\n" + + " PARAMETER[\"False northing\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8807]]],\n" + + " CS[Cartesian,2],\n" + + " AXIS[\"easting (X)\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " AXIS[\"northing (Y)\",north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " SCOPE[\"Web mapping and visualisation.\"],\n" + + " AREA[\"World between 85.06°S and 85.06°N.\"],\n" + + " BBOX[-85.06,-180,85.06,180],\n" + + " ID[\"EPSG\",3785]]"), 6.388019798183263E-6, 6.388019798183263E-6), 128.0d, 128.0d)); + } + + @Test + @SneakyThrows(ParseException.class) + @SuppressWarnings("deprecation") + public void testEPSG3785SameAs3857() { + testProjectionAccuracy( + new EPSG3785(), + new OffsetProjectionTransform(new ScaleProjectionTransform(new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Pseudo-Mercator\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"unnamed\",\n" + + " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" + + " ID[\"EPSG\",1024]],\n" + + " PARAMETER[\"Latitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8801]],\n" + + " PARAMETER[\"Longitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8802]],\n" + + " PARAMETER[\"False easting\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8806]],\n" + + " PARAMETER[\"False northing\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8807]]],\n" + + " CS[Cartesian,2],\n" + + " AXIS[\"easting (X)\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " AXIS[\"northing (Y)\",north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " SCOPE[\"Web mapping and visualisation.\"],\n" + + " AREA[\"World between 85.06°S and 85.06°N.\"],\n" + + " BBOX[-85.06,-180,85.06,180],\n" + + " ID[\"EPSG\",3857]]"), 6.388019798183263E-6, 6.388019798183263E-6), 128.0d, 128.0d), + 1e-12d); //this is slightly less accurate + + testProjectionAccuracy( + new EPSG3785(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Terra++ Scaled Pseudo-Mercator\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"unnamed\",\n" + + " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" + + " ID[\"EPSG\",1024]],\n" + + " PARAMETER[\"Latitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8801]],\n" + + " PARAMETER[\"Longitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8802]],\n" + //porkman added this: begin + + " PARAMETER[\"Scale factor at natural origin\",6.388019798183263E-6,\n" + + " SCALEUNIT[\"unity\",1],\n" + + " ID[\"EPSG\",8805]],\n" + //porkman added this: end + //porkman changed these parameter values from 0 to 128: begin + + " PARAMETER[\"False easting\",128.0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8806]],\n" + + " PARAMETER[\"False northing\",128.0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8807]]],\n" + //porkman changed these parameter values from 0 to 128: end + + " CS[Cartesian,2],\n" + + " AXIS[\"easting (X)\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " AXIS[\"northing (Y)\",north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " SCOPE[\"Web mapping and visualisation.\"],\n" + + " AREA[\"World between 85.06°S and 85.06°N.\"],\n" + + " BBOX[-85.06,-180,85.06,180]]"), + 1e-12d); //this is slightly less accurate + } + + @Test + @SneakyThrows(ParseException.class) + public void testWebMercatorSameAs3857() { + testProjectionAccuracy( + new WebMercatorProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Terra++ Scaled Pseudo-Mercator (Web Mercator)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"unnamed\",\n" + + " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" + + " ID[\"EPSG\",1024]],\n" + + " PARAMETER[\"Latitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8801]],\n" + + " PARAMETER[\"Longitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8802]],\n" + //porkman added this: begin + + " PARAMETER[\"Scale factor at natural origin\",6.388019798183263E-6,\n" + + " SCALEUNIT[\"unity\",1],\n" + + " ID[\"EPSG\",8805]],\n" + //porkman added this: end + //porkman changed these parameter values from 0 to 128: begin + + " PARAMETER[\"False easting\",128.0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8806]],\n" + + " PARAMETER[\"False northing\",-128.0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8807]]],\n" + //porkman changed these parameter values from 0 to 128: end + + " CS[Cartesian,2],\n" + + " AXIS[\"easting (X)\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " AXIS[\"southing (Y)\",south,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " SCOPE[\"Web mapping and visualisation.\"],\n" + + " AREA[\"World between 85.06°S and 85.06°N.\"],\n" + + " BBOX[-85.06,-180,85.06,180]]"), + 1e-12d); //this is slightly less accurate + } + + @Test + @SneakyThrows(ParseException.class) + public void testEPSG4326AgainstReal() { + testProjectionAccuracy( + new SwapAxesProjectionTransform(new EPSG4326()), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "GEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " CS[ellipsoidal,2],\n" + + " AXIS[\"geodetic latitude (Lat)\",north,\n" + + " ORDER[1],\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " AXIS[\"geodetic longitude (Lon)\",east,\n" + + " ORDER[2],\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " SCOPE[\"Horizontal component of 3D system.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90,-180,90,180],\n" + + " ID[\"EPSG\",4326]]")); + } + + @Test + @SneakyThrows(ParseException.class) + public void testEPSG4326AgainstSwappedAxes() { + testProjectionAccuracy( + new EPSG4326(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "GEODCRS[\"WGS 84 / Reversed Axis Order\",\n" + + " DATUM[\"World Geodetic System 1984 / Reversed Axis Order\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " CS[ellipsoidal,2],\n" + + " AXIS[\"geodetic latitude (Lat)\",north,\n" + + " ORDER[2],\n" //porkman was here: changed this to 2 from 1 + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " AXIS[\"geodetic longitude (Lon)\",east,\n" + + " ORDER[1],\n" //porkman was here: changed this to 1 from 2 + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " SCOPE[\"Horizontal component of 3D system.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90,-180,90,180]]")); + + testProjectionAccuracy( + new EPSG4326(), + new SISProjectionWrapper(TPP_GEO_CRS)); + } + + @Test + @SneakyThrows(ParseException.class) + public void testEquirectangular() { + testProjectionAccuracy( + new EquirectangularProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "GEODCRS[\"WGS 84 / Reversed Axis Order / Terra++ Equirectangular\",\n" + + " DATUM[\"World Geodetic System 1984 / Reversed Axis Order\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " CS[ellipsoidal,2],\n" + + " AXIS[\"geodetic latitude (Lat)\",north,\n" + + " ORDER[2],\n" //porkman was here: changed this to 2 from 1 + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " AXIS[\"geodetic longitude (Lon)\",east,\n" + + " ORDER[1],\n" //porkman was here: changed this to 1 from 2 + + " ANGLEUNIT[\"degree\",0.0174532925199433]],\n" + + " SCOPE[\"Horizontal component of 3D system.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90,-180,90,180]]")); + + testProjectionAccuracy( + new EquirectangularProjection(), + new SISProjectionWrapper(TPP_GEO_CRS)); + } + + @Test + @SneakyThrows(ParseException.class) + public void testSinusoidal() { + testProjectionAccuracy( + new SinusoidalProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / Terra++ Sinusoidal (Unscaled)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"unnamed\",\n" + + " METHOD[\"Pseudo sinusoidal\"],\n" + + " PARAMETER[\"False easting\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8806]],\n" + + " PARAMETER[\"False northing\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8807]]],\n" + + " CS[Cartesian,2],\n" + + " AXIS[\"easting (X)\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"unnamed\", 111319.49079327358]],\n" + + " AXIS[\"northing (Y)\",north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"unnamed\", 111319.49079327358]],\n" + + " SCOPE[\"Horizontal component of 3D system.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90,-180,90,180]]"), + 1e-10d); //this is slightly less accurate than some of the others + + testProjectionAccuracy( + new SinusoidalProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + //TODO: this would be nicer using DERIVEDPROJCRS from WKT2:2019 + "FITTED_CS[\"WGS 84 / Reversed Axis Order / Terra++ Sinusoidal (Degrees)\",\n" + + " PARAM_MT[\"Affine\",\n" + + " METHOD[\"Affine\", ID[\"EPSG\", 9624]],\n" + + " PARAMETER[\"num_col\", 3],\n" + + " PARAMETER[\"num_row\", 3],\n" + + " PARAMETER[\"elt_0_0\", 111319.49079327358],\n" + + " PARAMETER[\"elt_0_1\", 0],\n" + + " PARAMETER[\"elt_1_0\", 0],\n" + + " PARAMETER[\"elt_1_1\", 111319.49079327358]],\n" + + " PROJCRS[\"WGS 84 / Reversed Axis Order / Terra++ Sinusoidal (Radians)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"unnamed\",\n" + + " METHOD[\"Pseudo sinusoidal\"],\n" + + " PARAMETER[\"False easting\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8806]],\n" + + " PARAMETER[\"False northing\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8807]]],\n" + + " CS[Cartesian,2],\n" + + " AXIS[\"easting (X)\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " AXIS[\"northing (Y)\",north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " SCOPE[\"Horizontal component of 3D system.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90,-180,90,180]]]"), + 1e-10d); //this is slightly less accurate than some of the others + + testProjectionAccuracy( + new SinusoidalProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / Terra++ Sinusoidal\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"Terra++ Sinusoidal\",\n" + + " METHOD[\"Terra++ Sinusoidal\"]],\n" + + " CS[Cartesian,2],\n" + + " AXIS[\"easting (X)\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " AXIS[\"northing (Y)\",north,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\", 1]],\n" + + " SCOPE[\"Horizontal component of 3D system.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90,-180,90,180]]"), + 1e-10d); //this is slightly less accurate than some of the others + + } + + @Test + @SneakyThrows(ParseException.class) + public void testCenteredMercator() { + testProjectionAccuracy( + new CenteredMercatorProjection(), + new SISProjectionWrapper(WKTStandard.WKT2_2015, + "PROJCRS[\"WGS 84 / Reversed Axis Order / Terra++ Scaled Centered Mercator (Pseudo-Mercator)\",\n" + + " BASEGEODCRS[\"WGS 84\",\n" + + " DATUM[\"World Geodetic System 1984\",\n" + + " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n" + + " LENGTHUNIT[\"metre\",1]]],\n" + + " PRIMEM[\"Greenwich\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + + " CONVERSION[\"unnamed\",\n" + + " METHOD[\"Popular Visualisation Pseudo Mercator\",\n" + + " ID[\"EPSG\",1024]],\n" + + " PARAMETER[\"Latitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8801]],\n" + + " PARAMETER[\"Longitude of natural origin\",0,\n" + + " ANGLEUNIT[\"degree\",0.0174532925199433],\n" + + " ID[\"EPSG\",8802]],\n" + //porkman added this: begin + + " PARAMETER[\"Scale factor at natural origin\",4.990640467330674E-8,\n" + + " SCALEUNIT[\"unity\",1],\n" + + " ID[\"EPSG\",8805]],\n" + //porkman added this: end + + " PARAMETER[\"False easting\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8806]],\n" + + " PARAMETER[\"False northing\",0,\n" + + " LENGTHUNIT[\"metre\",1],\n" + + " ID[\"EPSG\",8807]]],\n" + + " CS[Cartesian,2],\n" + + " AXIS[\"easting (X)\",east,\n" + + " ORDER[1],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " AXIS[\"southing (Y)\",south,\n" + + " ORDER[2],\n" + + " LENGTHUNIT[\"metre\",1]],\n" + + " SCOPE[\"Horizontal component of 3D system.\"],\n" + + " AREA[\"World.\"],\n" + + " BBOX[-90,-180,90,180]]"), + 1e-13d); //this is slightly less accurate + } + + private static boolean approxEquals(double[] a, double[] b) { + return approxEquals(a, b, DEFAULT_D); + } + + private static boolean approxEquals(double[] a, double[] b, double d) { + //noinspection ArrayEquality + if (a == b) { + return true; + } else if (a == null || b == null || a.length != b.length) { + return false; + } + + for (int i = 0, len = a.length; i < len; i++) { + if (!approxEquals(a[i], b[i], d)) { + return false; + } + } + + return true; + } + + private static boolean approxEquals(Matrix2 a, Matrix2 b) { + return approxEquals(a, b, DEFAULT_D); + } + + private static boolean approxEquals(Matrix2 a, Matrix2 b, double d) { + if (a == b) { + return true; + } else if (a == null || b == null) { + return false; + } + + return approxEquals(a.getElements(), b.getElements(), d); + } + + private static boolean veryApproximateEquals(Matrix2 a, Matrix2 b, double maxErrorInPercent, double d) { + if (a == b) { + return true; + } else if (a == null || b == null) { + return false; + } + + for (int row = 0; row < 2; row++) { + for (int col = 0; col < 2; col++) { + double da = a.getElement(row, col); + double db = b.getElement(row, col); + if (!approxEquals(da, db, d) && Math.abs(da - db) / Math.max(Math.abs(da), Math.abs(db)) >= maxErrorInPercent) { + return false; + } + } + } + return true; + } + + private static boolean approxEquals(double a, double b) { + return approxEquals(a, b, DEFAULT_D); + } + + private static boolean approxEquals(double a, double b, double d) { + return Math.abs(a - b) < d || a == b; + } +} diff --git a/src/test/resources/geojson/0.json b/src/test/resources/geojson/0.json new file mode 100644 index 00000000..c91a73bb --- /dev/null +++ b/src/test/resources/geojson/0.json @@ -0,0 +1,79 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 102.0, + 0.5 + ] + }, + "properties": { + "prop0": "value0" + } + }, + { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 102.0, + 0.0 + ], + [ + 103.0, + 1.0 + ], + [ + 104.0, + 0.0 + ], + [ + 105.0, + 1.0 + ] + ] + }, + "properties": { + "prop0": "value0", + "prop1": 0.0 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 100.0, + 0.0 + ], + [ + 101.0, + 0.0 + ], + [ + 101.0, + 1.0 + ], + [ + 100.0, + 1.0 + ], + [ + 100.0, + 0.0 + ] + ] + ] + }, + "properties": { + "prop0": "value0" + } + } + ] +} diff --git a/src/test/resources/geojson/1.json b/src/test/resources/geojson/1.json new file mode 100644 index 00000000..9b37589b --- /dev/null +++ b/src/test/resources/geojson/1.json @@ -0,0 +1,25 @@ +{ + "type": "MultiLineString", + "coordinates": [ + [ + [ + 170.0, + 45.0 + ], + [ + 180.0, + 45.0 + ] + ], + [ + [ + -180.0, + 45.0 + ], + [ + -170.0, + 45.0 + ] + ] + ] +} diff --git a/src/test/resources/geojson/2.json b/src/test/resources/geojson/2.json new file mode 100644 index 00000000..0dfeff5d --- /dev/null +++ b/src/test/resources/geojson/2.json @@ -0,0 +1,53 @@ +{ + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + 180.0, + 40.0 + ], + [ + 180.0, + 50.0 + ], + [ + 170.0, + 50.0 + ], + [ + 170.0, + 40.0 + ], + [ + 180.0, + 40.0 + ] + ] + ], + [ + [ + [ + -170.0, + 40.0 + ], + [ + -170.0, + 50.0 + ], + [ + -180.0, + 50.0 + ], + [ + -180.0, + 40.0 + ], + [ + -170.0, + 40.0 + ] + ] + ] + ] +}