Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18725261e3 | |||
|
|
cc1a0c1aca
|
||
|
|
f2a5efcec5
|
||
|
|
cfa784991b
|
||
|
|
14f8543d3d
|
||
|
|
5705749d12
|
||
| 1d0b814e77 | |||
|
|
ecc26f811a | ||
| 8cd93d866b | |||
|
|
6bf4034e1e | ||
| 977321ddb5 | |||
| bee96b5d9b | |||
|
|
6d21f0da6d | ||
|
|
445ccbcac0 | ||
| 225e5bfdc8 | |||
|
|
0c446f0d59 | ||
| 7c0b9bda80 | |||
|
|
eaa0278764 | ||
| eeac01f638 | |||
|
|
9f8d5aaa18 | ||
| cdb9d51257 | |||
| 2d6b895f57 | |||
|
|
80e191a50d | ||
| 4114185a73 | |||
| 24fe56c02a | |||
|
|
d83234cf5b | ||
|
|
e608b22c0c |
@@ -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
|
||||||
|
|||||||
@@ -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 |
@@ -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())
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
15
app/src/main/res/values-night/themes.xml
Normal file
15
app/src/main/res/values-night/themes.xml
Normal 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>
|
||||||
7
app/src/main/res/values-v21/themes.xml
Normal file
7
app/src/main/res/values-v21/themes.xml
Normal 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>
|
||||||
16
app/src/main/res/values/themes.xml
Normal file
16
app/src/main/res/values/themes.xml
Normal 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>
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
60
genenum.js
60
genenum.js
@@ -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")
|
||||||
54
gensvg.sh
54
gensvg.sh
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user