27 Commits
1.3a ... 1.3b

Author SHA1 Message Date
18725261e3 Update app/build.gradle 2026-06-06 19:34:25 +02:00
soraefir
cc1a0c1aca Fix region saving bug 2026-06-06 19:30:18 +02:00
soraefir
f2a5efcec5 Update saving logic 2026-06-06 18:54:09 +02:00
soraefir
cfa784991b Updated SVGs and improved scripting
# Conflicts:
#	yarn.lock
2026-06-06 18:23:41 +02:00
soraefir
14f8543d3d Fix zoom and topbar 2026-06-06 18:23:26 +02:00
soraefir
5705749d12 Fix Caspian Sea 2026-06-06 18:23:26 +02:00
bot
1d0b814e77 Merge pull request 'Lock file maintenance' (#513) from renovate/lock-file-maintenance into main 2026-06-06 04:01:52 +02:00
Renovate Bot
ecc26f811a Lock file maintenance 2026-06-06 02:01:48 +00:00
bot
8cd93d866b Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2.4.0' (#512) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main 2026-06-05 04:05:29 +02:00
Renovate Bot
6bf4034e1e Update plugin org.jetbrains.kotlin.plugin.serialization to v2.4.0 2026-06-05 02:02:00 +00:00
bot
977321ddb5 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.compose to v2.4.0' (#511) from renovate/org.jetbrains.kotlin.plugin.compose-2.x into main 2026-06-04 04:11:32 +02:00
bot
bee96b5d9b Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2.4.0' (#510) from renovate/org.jetbrains.kotlin.android-2.x into main 2026-06-04 04:11:19 +02:00
Renovate Bot
6d21f0da6d Update plugin org.jetbrains.kotlin.plugin.compose to v2.4.0 2026-06-04 02:11:18 +00:00
Renovate Bot
445ccbcac0 Update plugin org.jetbrains.kotlin.android to v2.4.0 2026-06-04 02:02:01 +00:00
bot
225e5bfdc8 Merge pull request 'Lock file maintenance' (#509) from renovate/lock-file-maintenance into main 2026-05-31 04:02:13 +02:00
Renovate Bot
0c446f0d59 Lock file maintenance 2026-05-31 02:02:04 +00:00
bot
7c0b9bda80 Merge pull request 'Update dependency mapshaper to v0.7.22' (#508) from renovate/mapshaper-0.x-lockfile into main 2026-05-30 04:02:10 +02:00
Renovate Bot
eaa0278764 Update dependency mapshaper to v0.7.22 2026-05-30 02:02:02 +00:00
bot
eeac01f638 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.2.1' (#504) from renovate/com.mikepenz-aboutlibraries-compose-m3-14.x into main 2026-05-30 04:01:32 +02:00
Renovate Bot
9f8d5aaa18 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.2.1 2026-05-30 02:01:26 +00:00
cdb9d51257 Update README.md 2026-05-29 19:01:20 +02:00
bot
2d6b895f57 Merge pull request 'Update dependency mapshaper to v0.7.21' (#507) from renovate/mapshaper-0.x-lockfile into main 2026-05-28 04:02:20 +02:00
Renovate Bot
80e191a50d Update dependency mapshaper to v0.7.21 2026-05-28 02:02:07 +00:00
bot
4114185a73 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v14.2.1' (#506) from renovate/com.mikepenz.aboutlibraries.plugin-14.x into main 2026-05-26 04:01:40 +02:00
bot
24fe56c02a Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v14.2.1' (#505) from renovate/com.mikepenz-aboutlibraries-core-14.x into main 2026-05-26 04:01:35 +02:00
Renovate Bot
d83234cf5b Update plugin com.mikepenz.aboutlibraries.plugin to v14.2.1 2026-05-26 02:01:33 +00:00
Renovate Bot
e608b22c0c Update dependency com.mikepenz:aboutlibraries-core to v14.2.1 2026-05-26 02:01:31 +00:00
20 changed files with 7907 additions and 19839 deletions

View File

@@ -73,7 +73,7 @@ Thanks to all contributors, the developers of our dependencies, and our users.
## 📝 License ## 📝 License
``` ```
Copyright (C) 2024 Helcel & MYDOLI Copyright (C) 2026 Helcel & MYDOLI
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View File

@@ -1,8 +1,8 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.21' id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.0'
id 'org.jetbrains.kotlin.plugin.compose' version '2.3.21' id 'org.jetbrains.kotlin.plugin.compose' version '2.4.0'
id 'com.mikepenz.aboutlibraries.plugin' version '14.2.0' id 'com.mikepenz.aboutlibraries.plugin' version '14.2.1'
} }
android { android {
@@ -15,7 +15,7 @@ android {
applicationId 'net.helcel.beans' applicationId 'net.helcel.beans'
minSdk = 28 minSdk = 28
targetSdk = 37 targetSdk = 37
versionName "1.3" versionName "1.3b"
versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1 versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1
} }
signingConfigs { signingConfigs {
@@ -106,10 +106,16 @@ dependencies {
implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.mikepenz:aboutlibraries:14.2.1' implementation 'com.mikepenz:aboutlibraries:14.2.1'
implementation 'com.mikepenz:aboutlibraries-compose-m3:14.2.0' implementation 'com.mikepenz:aboutlibraries-compose-m3:14.2.1'
implementation 'com.mikepenz:aboutlibraries-core:14.2.0' implementation 'com.mikepenz:aboutlibraries-core:14.2.1'
implementation platform('androidx.compose:compose-bom:2026.05.01') implementation platform('androidx.compose:compose-bom:2026.05.01')
debugImplementation 'androidx.compose.ui:ui-tooling:1.11.2' debugImplementation 'androidx.compose.ui:ui-tooling:1.11.2'
}
tasks.configureEach { task ->
if (task.name.startsWith("merge") && task.name.endsWith("Assets")) {
task.outputs.upToDateWhen { false }
}
} }

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 MiB

After

Width:  |  Height:  |  Size: 3.9 MiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.0 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 MiB

After

Width:  |  Height:  |  Size: 4.0 MiB

View File

@@ -50,12 +50,12 @@ class MainScreen : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
actionBar?.hide() actionBar?.hide()
Settings.start(this) Settings.start(this)
GeoLocImporter.importStates(this)
Data.loadData(this, Int.MIN_VALUE) Data.loadData(this, Int.MIN_VALUE)
GeoLocImporter.importStates(this)
setContent { setContent {
SysTheme { SysTheme {
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background).statusBarsPadding(),) { Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary).statusBarsPadding(),) {
AppNavHost(psvg, css) AppNavHost(psvg, css)
} }
} }
@@ -114,6 +114,7 @@ class MainScreen : ComponentActivity() {
PhotoView(ctx).apply { PhotoView(ctx).apply {
setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null) setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null)
setImageDrawable(drawable) setImageDrawable(drawable)
maximumScale = 32f
scaleType = ImageView.ScaleType.FIT_CENTER scaleType = ImageView.ScaleType.FIT_CENTER
} }
}, modifier = Modifier.fillMaxSize()) }, modifier = Modifier.fillMaxSize())

View File

@@ -168,8 +168,10 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) {
EditPlaceDialog(true) { EditPlaceDialog(true) {
showEdit = false showEdit = false
val g = Data.selected_group val g = Data.selected_group
if (it && g != null) if (it && g != null) {
Data.visits.reassignAllVisitedToGroup(g.key) Data.visits.reassignAllVisitedToGroup(g.key)
Data.saveData()
}
} }
LazyColumn( LazyColumn(
@@ -216,8 +218,10 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) {
deleteMode = true, deleteMode = true,
onDismiss = { onDismiss = {
val g = Data.selected_group val g = Data.selected_group
if (g != null) if (g != null) {
Data.visits.reassignAllVisitedToGroup(g.key) Data.visits.reassignAllVisitedToGroup(g.key)
Data.saveData()
}
showDialog = false showDialog = false
}) })
} }

View File

@@ -54,27 +54,51 @@ fun EditPlaceScreenPreview(){
EditPlaceScreen(Group.EEE) EditPlaceScreen(Group.EEE)
} }
fun syncVisited(loc: GeoLoc?=World.WWW){ fun syncVisited(loc: GeoLoc?=World.WWW): Boolean {
var changed = false
loc?.children?.forEach { tt -> loc?.children?.forEach { tt ->
tt.children.forEach {itc-> tt.children.forEach {itc->
if(Data.visits.getVisited(itc) in listOf(AUTO_GROUP,NO_GROUP)) { if(Data.visits.getVisited(itc) in listOf(AUTO_GROUP,NO_GROUP)) {
if(itc.children.any { c -> Data.visits.getVisited(c) != NO_GROUP }) val newState = if(itc.children.any { c -> Data.visits.getVisited(c) != NO_GROUP })
Data.visits.setVisited(itc, AUTO_GROUP) AUTO_GROUP
else else
Data.visits.setVisited(itc, NO_GROUP) NO_GROUP
if (Data.visits.getVisited(itc) != newState) {
Data.visits.setVisited(itc, newState)
changed = true
}
} }
} }
if(Data.visits.getVisited(tt) in listOf(AUTO_GROUP,NO_GROUP)) { if(Data.visits.getVisited(tt) in listOf(AUTO_GROUP,NO_GROUP)) {
if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP }) val newState = if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP })
Data.visits.setVisited(tt, AUTO_GROUP) AUTO_GROUP
else else
Data.visits.setVisited(tt, NO_GROUP) NO_GROUP
if (Data.visits.getVisited(tt) != newState) {
Data.visits.setVisited(tt, newState)
changed = true
}
} }
} }
// Sync World from Continents
if (loc != null && Data.visits.getVisited(loc) in listOf(AUTO_GROUP, NO_GROUP)) {
val newState = if(loc.children.any { Data.visits.getVisited(it) != NO_GROUP })
AUTO_GROUP
else
NO_GROUP
if (Data.visits.getVisited(loc) != newState) {
Data.visits.setVisited(loc, newState)
changed = true
}
}
return changed
} }
@Composable @Composable
fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) { fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
val visits by Data.visits.visitsFlow.collectAsState()
var showEdit by remember { mutableStateOf(false) } var showEdit by remember { mutableStateOf(false) }
val tabs : SnapshotStateList<GeoLoc> = remember { mutableStateListOf(loc) } val tabs : SnapshotStateList<GeoLoc> = remember { mutableStateListOf(loc) }
val ctx = LocalContext.current val ctx = LocalContext.current
@@ -84,7 +108,12 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
selectedTab = tabs.lastIndex selectedTab = tabs.lastIndex
} }
SideEffect { SideEffect {
syncVisited() // visits is used to trigger sync whenever data changes
if (visits.isNotEmpty() || true) {
if (syncVisited()) {
Data.saveData()
}
}
} }
BackHandler { BackHandler {
if (tabs.size > 1) tabs.removeAt(tabs.lastIndex) if (tabs.size > 1) tabs.removeAt(tabs.lastIndex)
@@ -95,6 +124,7 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
showEdit = false showEdit = false
if (it) { if (it) {
Data.visits.setVisited(Data.selected_geoloc, NO_GROUP) Data.visits.setVisited(Data.selected_geoloc, NO_GROUP)
syncVisited()
Data.saveData() Data.saveData()
if (Data.selected_geoloc!=null && Data.selected_geoloc!!.children.any { itc-> Data.visits.getVisited(itc) != NO_GROUP }) { if (Data.selected_geoloc!=null && Data.selected_geoloc!!.children.any { itc-> Data.visits.getVisited(itc) != NO_GROUP }) {
@@ -103,6 +133,7 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
} }
if (Data.selected_group != null && Data.selected_geoloc != null) { if (Data.selected_group != null && Data.selected_geoloc != null) {
Data.visits.setVisited(Data.selected_geoloc, Data.selected_group!!.key) Data.visits.setVisited(Data.selected_geoloc, Data.selected_group!!.key)
syncVisited()
Data.saveData() Data.saveData()
} }
Data.selected_geoloc = null Data.selected_geoloc = null
@@ -144,12 +175,14 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
Data.visits.getVisited(itc)!= NO_GROUP } == true) AUTO_GROUP Data.visits.getVisited(itc)!= NO_GROUP } == true) AUTO_GROUP
else NO_GROUP else NO_GROUP
) )
Data.saveData()
Data.selected_group = null Data.selected_group = null
} else { } else {
Data.selected_group = null Data.selected_group = null
showEdit=true showEdit=true
} }
syncVisited()
Data.saveData()
}) })
} }

View File

@@ -43,14 +43,14 @@ object Data {
if (groups.size() == 0) { if (groups.size() == 0) {
groups.setGroup(DEFAULT_GROUP, "Visited", groups.setGroup(DEFAULT_GROUP, "Visited",
ContextCompat.getColor(ctx, R.color.blue).toDrawable()) ContextCompat.getColor(ctx, R.color.blue).toDrawable())
saveData()
} }
saveData()
} }
fun saveData() { fun saveData() {
if(groups.id != visits.id) return if(groups.id != visits.id) return
val id = groups.id val id = groups.id
sharedPreferences.edit { sharedPreferences.edit(commit=true) {
putString("groups_$id", groupsSerial.writeTo(groups)) putString("groups_$id", groupsSerial.writeTo(groups))
putString("visits_$id", visitsSerial.writeTo(visits)) putString("visits_$id", visitsSerial.writeTo(visits))
} }

View File

@@ -32,12 +32,18 @@ class Groups(val id: Int, @SerialName("grps") private val groups: HashMap<Int, G
val groupsFlow: StateFlow<List<Group>> = _groupsFlow.asStateFlow() val groupsFlow: StateFlow<List<Group>> = _groupsFlow.asStateFlow()
fun setGroup(key: Int, name: String, col: ColorDrawable) { fun setGroup(key: Int, name: String, col: ColorDrawable) {
val old = groups[key]
if (old != null && old.name == name && old.color.color == col.color) return
groups[key] = Group(key, name, col) groups[key] = Group(key, name, col)
_groupsFlow.value = groups.values.toList() updateFlow()
} }
fun deleteGroup(key: Int) { fun deleteGroup(key: Int) {
groups.remove(key) groups.remove(key)
updateFlow()
}
private fun updateFlow() {
_groupsFlow.value = groups.values.toList() _groupsFlow.value = groups.values.toList()
} }

View File

@@ -19,24 +19,25 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) {
val visitsFlow: StateFlow<Map<String,Int>> = _visitsFlow val visitsFlow: StateFlow<Map<String,Int>> = _visitsFlow
fun setVisited(key: GeoLoc?, b: Int) { fun setVisited(key: GeoLoc?, b: Int) {
if (key == null) if (key == null || locs[key.code] == b)
return return
_visitsFlow.value = _visitsFlow.value.toMutableMap().apply {
this[key.code] = b
}
locs[key.code] = b locs[key.code] = b
updateFlow()
}
private fun updateFlow() {
_visitsFlow.value = locs.toMap()
} }
fun deleteVisited(key: Int) { fun deleteVisited(key: Int) {
val keysToDelete = locs val keysToDelete = locs
.filter { it.value == key } .filter { it.value == key }
.map { it.key } .map { it.key }
_visitsFlow.value = _visitsFlow.value.toMutableMap().apply { if (keysToDelete.isEmpty()) return
keysToDelete.forEach { this.remove(it)}
}
keysToDelete.forEach { keysToDelete.forEach {
locs.remove(it) locs.remove(it)
} }
updateFlow()
} }
fun getVisited(key: GeoLoc): Int { fun getVisited(key: GeoLoc): Int {
@@ -60,13 +61,19 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) {
} }
fun reassignAllVisitedToGroup(group: Int) { fun reassignAllVisitedToGroup(group: Int) {
var changed = false
val keys = locs.filter { (_, grp) -> val keys = locs.filter { (_, grp) ->
grp !in listOf(NO_GROUP, AUTO_GROUP) grp !in listOf(NO_GROUP, AUTO_GROUP)
}.keys }.keys
keys.forEach { keys.forEach {
locs[it] = group if (locs[it] != group) {
locs[it] = group
changed = true
}
}
if (changed) {
updateFlow()
} }
_visitsFlow.value = locs
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Beans" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Status bar color in dark mode -->
<item name="android:statusBarColor">@color/blue</item>
<!-- Window background in dark mode -->
<item name="android:windowBackground">@color/darkgray</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="colorPrimary">@color/blue</item>
<item name="colorPrimaryDark">@color/blue</item>
<item name="colorAccent">@color/blue</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Beans" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@color/blue</item>
<item name="android:windowBackground">@color/blue</item>
</style>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Beans" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Status bar color -->
<item name="android:statusBarColor">@color/blue</item>
<!-- Window background - shows through statusBarsPadding() area -->
<item name="android:windowBackground">@color/blue</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<!-- Optional: Primary colors for the theme -->
<item name="colorPrimary">@color/blue</item>
<item name="colorPrimaryDark">@color/blue</item>
<item name="colorAccent">@color/blue</item>
</style>
</resources>

View File

@@ -2,5 +2,5 @@
plugins { plugins {
id 'com.android.application' version '9.2.1' apply false id 'com.android.application' version '9.2.1' apply false
id 'com.android.library' version '9.2.1' apply false id 'com.android.library' version '9.2.1' apply false
id 'org.jetbrains.kotlin.android' version '2.3.21' apply false id 'org.jetbrains.kotlin.android' version '2.4.0' apply false
} }

View File

@@ -1,8 +1,10 @@
#!/bin/node #!/bin/node
import {readFileSync, existsSync} from 'fs' import {readFileSync,writeFileSync,existsSync} from 'fs'
import area from '@turf/area' import area from '@turf/area'
import * as turf from '@turf/turf' import * as turf from '@turf/turf'
import * as path from 'path';
import { JSDOM } from 'jsdom';
const countries = const countries =
@@ -24,7 +26,7 @@ const countries =
const groups = { const groups = {
"EEE":["ALB","AND","AUT","BLR","BEL","BIH","BGR","HRV","CYP","CZE","DNK","EST","FIN","FRA","DEU","GRC","HUN","ISL","IRL","ITA","KAZ","XKO","LVA","LIE","LTU","LUX","MLT","MDA","MCO","MNE","NLD","MKD","NOR","POL","PRT","ROU","RUS","SMR","SRB","SVK","SVN","ESP","SWE","CHE","UKR","GBR","VAT","XAD"], "EEE":["ALA","ALB","AND","AUT","BLR","BEL","BIH","BGR","HRV","CYP","CZE","DNK","EST","FIN","FRA","DEU","GIB","GGY","GRC","HUN","ISL","IRL","IMN","JEY","ITA","KAZ","XKO","LVA","LIE","LTU","LUX","MLT","MDA","MCO","MNE","NLD","MKD","NOR","SJM","POL","PRT","ROU","RUS","SMR","SRB","SVK","SVN","ESP","SWE","CHE","UKR","GBR","VAT","XAD"],
"ABB":["AFG","ARM","AZE","BHR","BGD","BTN","BRN","KHM","CHN","GEO","HKG","IND","IDN","IRN","IRQ","ISR","JPN","JOR","KWT","KGZ","LAO","LBN","MAC","MYS","MDV","MNG","MMR","NPL","PRK","OMN","PAK","PSE","PHL","QAT","SAU","SGP","KOR","LKA","SYR","TWN","TJK","THA","TLS","TUR","TKM","ARE","UZB","VNM","YEM","ZNC"], "ABB":["AFG","ARM","AZE","BHR","BGD","BTN","BRN","KHM","CHN","GEO","HKG","IND","IDN","IRN","IRQ","ISR","JPN","JOR","KWT","KGZ","LAO","LBN","MAC","MYS","MDV","MNG","MMR","NPL","PRK","OMN","PAK","PSE","PHL","QAT","SAU","SGP","KOR","LKA","SYR","TWN","TJK","THA","TLS","TUR","TKM","ARE","UZB","VNM","YEM","ZNC"],
"FFF":["DZA","AGO","BDI","BEN","BWA","BFA","BDI","CPV","CMR","CAF","TCD","COM","COG","COD","CIV","DJI","EGY","GNQ","ERI","SWZ","ETH","GAB","GMB","GHA","GIN","GNB","KEN","LSO","LBR","LBY","MDG","MWI","MLI","MRT","MUS","MYT","MAR","MOZ","NAM","NER","NGA","COD","REU","RWA","STP","SEN","SYC","SLE","SOM","ZAF","SSD","SHN","SDN","TZA","TGO","TUN","UGA","COD","ZMB","ZWE","ESH"], "FFF":["DZA","AGO","BDI","BEN","BWA","BFA","BDI","CPV","CMR","CAF","TCD","COM","COG","COD","CIV","DJI","EGY","GNQ","ERI","SWZ","ETH","GAB","GMB","GHA","GIN","GNB","KEN","LSO","LBR","LBY","MDG","MWI","MLI","MRT","MUS","MYT","MAR","MOZ","NAM","NER","NGA","COD","REU","RWA","STP","SEN","SYC","SLE","SOM","ZAF","SSD","SHN","SDN","TZA","TGO","TUN","UGA","COD","ZMB","ZWE","ESH"],
"NNN":["ABW","AIA","ATG","BHS","BRB","BLZ","BMU","VGB","CAN","CYM","CRI","CUB","CUW","DMA","DOM","SLV","GRL","GRD","GLP","GTM","HTI","HND","JAM","MTQ","MEX","MSR","ANT","CUW","NIC","PAN","PRI","KNA","LCA","MAF","SPM","VCT","TTO","TCA","USA","XCL"], "NNN":["ABW","AIA","ATG","BHS","BRB","BLZ","BMU","VGB","CAN","CYM","CRI","CUB","CUW","DMA","DOM","SLV","GRL","GRD","GLP","GTM","HTI","HND","JAM","MTQ","MEX","MSR","ANT","CUW","NIC","PAN","PRI","KNA","LCA","MAF","SPM","VCT","TTO","TCA","USA","XCL"],
@@ -32,7 +34,6 @@ const groups = {
"UUU":["ASM","AUS","COK","FJI","PYF","GUM","KIR","MHL","FSM","NRU","NCL","NZL","NIU","NFK","MNP","PLW","PNG","PCN","SLB","TKL","TON","TUV","VUT","WLF"], "UUU":["ASM","AUS","COK","FJI","PYF","GUM","KIR","MHL","FSM","NRU","NCL","NZL","NIU","NFK","MNP","PLW","PNG","PCN","SLB","TKL","TON","TUV","VUT","WLF"],
"XXX":[ "XXX":[
"ATA", // Antarctica: not in any other region "ATA", // Antarctica: not in any other region
"ALA",// Åland Islands: an autonomous region of Finland, but not a member of the EU or UN
"BES",// Bonaire, Sint Eustatius and Saba: special municipalities of the Netherlands in the Caribbean "BES",// Bonaire, Sint Eustatius and Saba: special municipalities of the Netherlands in the Caribbean
"BVT",// Bouvet Island: an uninhabited territory of Norway in the South Atlantic "BVT",// Bouvet Island: an uninhabited territory of Norway in the South Atlantic
"IOT",// British Indian Ocean Territory: a British overseas territory in the Indian Ocean "IOT",// British Indian Ocean Territory: a British overseas territory in the Indian Ocean
@@ -40,16 +41,11 @@ const groups = {
"CCK",// Cocos (Keeling) Islands: an Australian external territory in the Indian Ocean "CCK",// Cocos (Keeling) Islands: an Australian external territory in the Indian Ocean
"FRO",// Faroe Islands: an autonomous region of Denmark "FRO",// Faroe Islands: an autonomous region of Denmark
"ATF",// French Southern and Antarctic Lands: a territory of France located in the southern Indian Ocean "ATF",// French Southern and Antarctic Lands: a territory of France located in the southern Indian Ocean
"GIB",// Gibraltar: a British overseas territory located at the southern tip of the Iberian Peninsula
"GGY",// Guernsey: a British Crown dependency in the English Channel
"HMD",// Heard Island and McDonald Islands: an uninhabited Australian external territory in the southern Indian Ocean "HMD",// Heard Island and McDonald Islands: an uninhabited Australian external territory in the southern Indian Ocean
"IMN",// Isle of Man: a British Crown dependency located in the Irish Sea
"JEY",// Jersey: a British Crown dependency located in the English Channel
"BLM",// Saint Barthélemy: an overseas collectivity of France in the Caribbean "BLM",// Saint Barthélemy: an overseas collectivity of France in the Caribbean
"WSM",// Samoa: an independent island nation in the South Pacific "WSM",// Samoa: an independent island nation in the South Pacific
"SXM",// Sint Maarten: a constituent country of the Kingdom of the Netherlands in the Caribbean "SXM",// Sint Maarten: a constituent country of the Kingdom of the Netherlands in the Caribbean
"SGS",// South Georgia and the South Sandwich Islands: a British overseas territory in the southern Atlantic Ocean "SGS",// South Georgia and the South Sandwich Islands: a British overseas territory in the southern Atlantic Ocean
"SJM",// Svalbard and Jan Mayen: an archipelago administered by Norway
"UMI",// United States Minor Outlying Islands: a collection of nine insular areas of the United States "UMI",// United States Minor Outlying Islands: a collection of nine insular areas of the United States
"VIR",// United States Virgin Islands: an unincorporated territory of the United States in the Caribbean "VIR",// United States Virgin Islands: an unincorporated territory of the United States in the Caribbean
] ]
@@ -166,4 +162,52 @@ async function run(){
} }
} }
function fixSvg(svgPath) {
const countryToRegion = {};
for (const [region, countries] of Object.entries(groups)) {
countries.forEach(country => countryToRegion[country] = region);
}
const absoluteInputPath = path.resolve(svgPath);
if (!existsSync(absoluteInputPath)) {
throw new Error(`Input file not found at: ${absoluteInputPath}`);
}
const svgContent = readFileSync(absoluteInputPath, 'utf8');
const dom = new JSDOM(svgContent, { contentType: 'image/svg+xml' });
const document = dom.window.document;
const svgRoot = document.querySelector('svg');
if (!svgRoot) {
throw new Error("Invalid or empty SVG structure encountered.");
}
if (svgRoot.getAttribute('data-processed') === 'true') {
console.log(`Skipping: File at "${svgPath}" has already been processed.`);
return;
}
const elementGroups = Array.from(svgRoot.querySelectorAll('g'));
elementGroups.forEach(group => {
const currentId = group.getAttribute('id') || '';
const baseIsoCode = currentId.replace(/\d+$/, '');
const regionKey = countryToRegion[baseIsoCode] || 'XXXX';
let regionGroup = svgRoot.querySelector(`g[id="${regionKey}"]`);
if (!regionGroup) {
regionGroup = document.createElementNS('http://w3.org', 'g');
regionGroup.setAttribute('id', regionKey);
svgRoot.appendChild(regionGroup);
}
regionGroup.appendChild(group);
});
svgRoot.setAttribute('data-processed', 'true');
const absoluteOutputPath = path.resolve(svgPath);
let cleanXmlString = svgRoot.outerHTML;
cleanXmlString = cleanXmlString.replace(/[\r\n]+/g, '');
cleanXmlString = cleanXmlString.replace(/xmlns="http:\/\/w3\.org"\s?/g, '');
cleanXmlString = cleanXmlString.replace(/xmlns="http:\/\/www\.w3\.org\/2000\/svg"\s?/g, '');
cleanXmlString = cleanXmlString.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
writeFileSync(absoluteOutputPath, cleanXmlString, 'utf8');
}
run() run()
fixSvg("./app/src/main/assets/loxim01.svg")
fixSvg("./app/src/main/assets/webmercator01.svg")
fixSvg("./app/src/main/assets/aeqd01.svg")

View File

@@ -8,10 +8,11 @@ GADM_BASEPATH="https://geodata.ucdavis.edu/gadm"
mapshaper="./node_modules/mapshaper/bin/mapshaper" mapshaper="./node_modules/mapshaper/bin/mapshaper"
ATA_URL="https://media.githubusercontent.com/media/wmgeolab/geoBoundaries/905b0baf5f4fb3b9ccf45293647dcacdb2b799d4/releaseData/gbOpen/ATA/ADM0/geoBoundaries-ATA-ADM0_simplified.geojson" ATA_URL="https://media.githubusercontent.com/media/wmgeolab/geoBoundaries/905b0baf5f4fb3b9ccf45293647dcacdb2b799d4/releaseData/gbOpen/ATA/ADM0/geoBoundaries-ATA-ADM0_simplified.geojson"
# Caspian Sea: "XCA"
countries=( countries=(
"AFG" "XAD" "ALA" "ALB" "DZA" "ASM" "AND" "AGO" "AIA" "ATG" "ARG" "ARM" "ABW" "AUS" "AUT" "AZE" "AFG" "XAD" "ALA" "ALB" "DZA" "ASM" "AND" "AGO" "AIA" "ATG" "ARG" "ARM" "ABW" "AUS" "AUT" "AZE"
"BHS" "BHR" "BGD" "BRB" "BLR" "BEL" "BLZ" "BEN" "BMU" "BTN" "BOL" "BES" "BIH" "BWA" "BVT" "BRA" "IOT" "VGB" "BRN" "BGR" "BFA" "BDI" "KHM" "BHS" "BHR" "BGD" "BRB" "BLR" "BEL" "BLZ" "BEN" "BMU" "BTN" "BOL" "BES" "BIH" "BWA" "BVT" "BRA" "IOT" "VGB" "BRN" "BGR" "BFA" "BDI" "KHM"
"CMR" "CAN" "CPV" "XCA" "CYM" "CAF" "TCD" "CHL" "CHN" "CXR" "XCL" "CCK" "COL" "COM" "COK" "CRI" "CIV" "HRV" "CUB" "CUW" "CYP" "CZE" "COD" "CMR" "CAN" "CPV" "CYM" "CAF" "TCD" "CHL" "CHN" "CXR" "XCL" "CCK" "COL" "COM" "COK" "CRI" "CIV" "HRV" "CUB" "CUW" "CYP" "CZE" "COD"
"DNK" "DJI" "DMA" "DOM" "ECU" "EGY" "SLV" "GNQ" "ERI" "EST" "ETH" "FLK" "FRO" "FJI" "FIN" "FRA" "GUF" "PYF" "ATF" "DNK" "DJI" "DMA" "DOM" "ECU" "EGY" "SLV" "GNQ" "ERI" "EST" "ETH" "FLK" "FRO" "FJI" "FIN" "FRA" "GUF" "PYF" "ATF"
"GAB" "GMB" "GEO" "DEU" "GHA" "GIB" "GRC" "GRL" "GRD" "GLP" "GUM" "GTM" "GGY" "GIN" "GNB" "GUY" "HTI" "HMD" "HND" "HUN" "GAB" "GMB" "GEO" "DEU" "GHA" "GIB" "GRC" "GRL" "GRD" "GLP" "GUM" "GTM" "GGY" "GIN" "GNB" "GUY" "HTI" "HMD" "HND" "HUN"
"ISL" "IND" "IDN" "IRN" "IRQ" "IRL" "IMN" "ISR" "ITA" "JAM" "JPN" "JEY" "JOR" "KAZ" "KEN" "KIR" "XKO" "KWT" "KGZ" "ISL" "IND" "IDN" "IRN" "IRQ" "IRL" "IMN" "ISR" "ITA" "JAM" "JPN" "JEY" "JOR" "KAZ" "KEN" "KIR" "XKO" "KWT" "KGZ"
@@ -115,6 +116,47 @@ toSVG_1() {
"$mapshaper" -i combine-files ${input_files[@]} -proj aeqd +lat_0=90 -simplify 0.005 weighted keep-shapes resolution=1200x1200 -o ./app/src/main/assets/aeqd1.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID "$mapshaper" -i combine-files ${input_files[@]} -proj aeqd +lat_0=90 -simplify 0.005 weighted keep-shapes resolution=1200x1200 -o ./app/src/main/assets/aeqd1.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID
} }
generate_svg_map() {
local OUT_FILE="$1" # First argument: Output destination path
local PROJ_ARGS="$2" # Second argument: Projection parameters
shift 2 # Remove the first two arguments, leaving only the files
local FILES_TO_RUN=("$@") # Capture all remaining arguments as the file array
echo "Generating: $OUT_FILE using projection [$PROJ_ARGS]"
echo "Processing ${#FILES_TO_RUN[@]} files..."
local JS_PIPELINE_LOGIC="
const conflicts = {
'Z01': 'IND', 'Z04': 'IND', 'Z05': 'IND', 'Z07': 'IND', 'Z09': 'IND',
'Z02': 'CHN', 'Z03': 'CHN','Z08': 'CHN',
'Z06': 'PAK'
};
let rawCode = GID_0 || 'UNK';
let cCode = conflicts[rawCode] ? conflicts[rawCode] : rawCode;
let isGidMissing = (!GID || GID === 'undefined' || GID === 'null' || GID === '');
if (isGidMissing) {
COUNTRY_GROUP = cCode+'2';
} else {
COUNTRY_GROUP = cCode+'1';
}
"
"$mapshaper" -i "${FILES_TO_RUN[@]}" combine-files \
-snap \
-merge-layers force\
-proj $PROJ_ARGS densify \
-simplify 2% weighted keep-shapes \
-filter-islands min-area=0 \
-sort 'this.area' ascending \
-each "$JS_PIPELINE_LOGIC" \
-split COUNTRY_GROUP \
-o "$OUT_FILE" \
format=svg \
id-field=GID \
precision=0.1 \
target=*
}
toSVG_01() { toSVG_01() {
input_files=() input_files=()
@@ -136,9 +178,9 @@ toSVG_01() {
fi fi
done done
"$mapshaper" -i combine-files ${input_files[@]} snap -proj loxim densify -simplify 0.001 weighted keep-shapes -o ./app/src/main/assets/loxim01.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID generate_svg_map "./app/src/main/assets/loxim01.svg" "loxim" "${input_files[@]}"
"$mapshaper" -i combine-files ${input_files[@]} snap -proj webmercator densify -simplify 0.001 weighted keep-shapes -o ./app/src/main/assets/webmercator01.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID generate_svg_map "./app/src/main/assets/webmercator01.svg" "webmercator" "${input_files[@]}"
"$mapshaper" -i combine-files ${input_files[@]} snap -proj aeqd +lat_0=90 densify -simplify 0.001 weighted keep-shapes -o ./app/src/main/assets/aeqd01.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID generate_svg_map "./app/src/main/assets/aeqd01.svg" "aeqd +lat_0=90" "${input_files[@]}"
} }
do_1() { do_1() {
@@ -153,10 +195,12 @@ do_0() {
do do
download_0 "$country" download_0 "$country"
done done
wget -q -O "./temp/1/ATA.json" "$ATA_URL" wget -q -O "./temp/0/ATA.json" "$ATA_URL"
} }
# do_0 # do_0
# do_1 # do_1
# toSVG_0 # toSVG_0
# toSVG_1 # toSVG_1
toSVG_01 toSVG_01
#CUW, CCK, XCL, CXR, IOT, BVT, ABW, FLK, GIB, HMD, KIR, SXM, MDV, MCO, NIU, NFK, PCN, MAF, SGS, VAT

View File

@@ -3,7 +3,7 @@
"@turf/area": "^7.3.5", "@turf/area": "^7.3.5",
"@turf/turf": "^7.3.5", "@turf/turf": "^7.3.5",
"jsdom": "^29.1.1", "jsdom": "^29.1.1",
"mapshaper": "^0.7.19" "mapshaper": "^0.7.22"
}, },
"type": "module" "type": "module"
} }

8037
yarn.lock

File diff suppressed because it is too large Load Diff