1 Commits

Author SHA1 Message Date
Renovate Bot 7c7f60247f Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.2.1 2026-05-25 02:01:29 +00:00
29 changed files with 16910 additions and 4071 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
fetch-depth: 0
+1 -1
View File
@@ -73,7 +73,7 @@ Thanks to all contributors, the developers of our dependencies, and our users.
## 📝 License
```
Copyright (C) 2026 Helcel & MYDOLI
Copyright (C) 2024 Helcel & MYDOLI
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
+10 -16
View File
@@ -1,8 +1,8 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.0'
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.0'
id 'com.mikepenz.aboutlibraries.plugin' version '15.0.3'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.21'
id 'org.jetbrains.kotlin.plugin.compose' version '2.3.21'
id 'com.mikepenz.aboutlibraries.plugin' version '14.2.0'
}
android {
@@ -15,7 +15,7 @@ android {
applicationId 'net.helcel.beans'
minSdk = 28
targetSdk = 37
versionName "1.5"
versionName "1.2b"
versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1
}
signingConfigs {
@@ -87,7 +87,7 @@ aboutLibraries {
dependencies {
implementation 'androidx.compose.material3:material3:1.4.0'
implementation "androidx.compose.material:material:1.11.4"
implementation "androidx.compose.material:material:1.11.2"
implementation 'androidx.compose.material:material-icons-extended:1.7.8'
implementation 'androidx.navigation:navigation-compose:2.9.8'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.5'
@@ -105,17 +105,11 @@ dependencies {
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.mikepenz:aboutlibraries:14.2.1'
implementation 'com.mikepenz:aboutlibraries-compose-m3:15.0.3'
implementation 'com.mikepenz:aboutlibraries-core:15.0.3'
implementation 'com.mikepenz:aboutlibraries:14.2.0'
implementation 'com.mikepenz:aboutlibraries-compose-m3:14.2.1'
implementation 'com.mikepenz:aboutlibraries-core:14.2.0'
implementation platform('androidx.compose:compose-bom:2026.06.01')
debugImplementation 'androidx.compose.ui:ui-tooling:1.11.4'
}
tasks.configureEach { task ->
if (task.name.startsWith("merge") && task.name.endsWith("Assets")) {
task.outputs.upToDateWhen { false }
}
implementation platform('androidx.compose:compose-bom:2026.05.01')
debugImplementation 'androidx.compose.ui:ui-tooling:1.11.2'
}
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 3.9 MiB

After

Width:  |  Height:  |  Size: 6.1 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: 3.7 MiB

After

Width:  |  Height:  |  Size: 6.0 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.0 MiB

After

Width:  |  Height:  |  Size: 6.1 MiB

@@ -9,7 +9,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
@@ -21,10 +20,7 @@ import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Percent
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
@@ -43,39 +39,32 @@ import net.helcel.beans.svg.SVGWrapper
class MainScreen : ComponentActivity() {
private var psvg by mutableStateOf<SVGWrapper?>(null)
private var css by mutableStateOf<CSSWrapper?>(null)
private lateinit var psvg: SVGWrapper
private lateinit var css: CSSWrapper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
actionBar?.hide()
Settings.start(this)
Data.loadData(this, Int.MIN_VALUE)
GeoLocImporter.importStates(this)
refreshProjection()
Data.loadData(this, Int.MIN_VALUE)
setContent {
SysTheme {
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary).statusBarsPadding(),) {
AppNavHost()
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
AppNavHost(psvg, css)
}
}
}
refreshProjection()
}
@Composable
fun AppNavHost() {
fun AppNavHost(psvg: SVGWrapper, css: CSSWrapper) {
val navController = rememberNavController()
NavHost(navController, startDestination = "main") {
composable("main") {
val currentPsvg = psvg
val currentCss = css
if (currentPsvg != null && currentCss != null) {
MainScreenC(currentPsvg, currentCss, navController)
}
}
composable("settings") { SettingsMainScreen { navController.navigate("main") } }
composable("main") { MainScreenC(psvg,css, navController) }
composable("settings") { SettingsMainScreen { navController.navigate("main")} }
composable("edit") { EditScreen { navController.navigate("main") } }
composable("stats") { StatsScreen { navController.navigate("main") } }
}
@@ -112,25 +101,18 @@ class MainScreen : ComponentActivity() {
@Composable
fun MapScreen(psvg: SVGWrapper, css: CSSWrapper) {
Box {
val cssContent = css.get()
val drawable = remember(psvg, css, cssContent) {
val opt: RenderOptions = RenderOptions.create()
opt.css(cssContent)
val opt: RenderOptions = RenderOptions.create()
opt.css(css.get())
val drawable = remember(psvg, css) {
PictureDrawable(psvg.get()?.renderToPicture(opt))
}
AndroidView(
factory = { ctx ->
PhotoView(ctx).apply {
setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null)
maximumScale = 64f
scaleType = ImageView.ScaleType.FIT_CENTER
}
},
update = { view ->
view.setImageDrawable(drawable)
},
modifier = Modifier.fillMaxSize()
)
AndroidView(factory = { ctx ->
PhotoView(ctx).apply {
setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null)
setImageDrawable(drawable)
scaleType = ImageView.ScaleType.FIT_CENTER
}
}, modifier = Modifier.fillMaxSize())
}
}
@@ -48,22 +48,23 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.preference.PreferenceManager
import net.helcel.beans.R
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.helper.Settings
import androidx.core.content.edit
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.preference.PreferenceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.helcel.beans.R
import net.helcel.beans.activity.sub.AboutScreen
import net.helcel.beans.activity.sub.EditPlaceColorDialog
import net.helcel.beans.activity.sub.EditPlaceDialog
import net.helcel.beans.activity.sub.LicenseScreen
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Settings
@Composable
fun SysTheme(
@@ -155,39 +156,150 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) {
val defaultTheme = stringResource(R.string.system)
val keyProjection = stringResource(R.string.key_projection)
val keyGroup = stringResource(R.string.key_group)
val keyRegional = stringResource(R.string.key_regional)
val keyCascadeStats = stringResource(R.string.key_cascade_stats)
val offString = stringResource(R.string.off)
val onString = stringResource(R.string.on)
var showEdit by remember { mutableStateOf(false) }
var theme by remember { mutableStateOf(prefs.getString(keyTheme, defaultTheme)!!) }
var projection by remember { mutableStateOf(prefs.getString(keyProjection, "default")!!) }
var groups by remember { mutableStateOf(prefs.getString(keyGroup, offString)!!) }
var regional by remember { mutableStateOf(prefs.getString(keyRegional, offString)!!) }
var cascadeStats by remember { mutableStateOf(prefs.getString(keyCascadeStats, offString)!!) }
var groups by remember { mutableStateOf(prefs.getString(keyGroup,offString)!!) }
var showGroupDialog by remember { mutableStateOf(false) }
var showRegionalDialog by remember { mutableStateOf(false) }
var showLoad by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
if (showGroupDialog) {
EditPlaceColorDialog(
deleteMode = true,
onDismiss = {
if(showEdit)
EditPlaceDialog(true) {
showEdit = false
val g = Data.selected_group
if (g != null) {
if (it && g != null)
Data.visits.reassignAllVisitedToGroup(g.key)
Data.groups.keepOnly(g.key)
Data.saveData()
groups = offString
prefs.edit { putString(keyGroup, offString) }
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.background(MaterialTheme.colors.background)
) {
item {
Text(
"Theme", style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
)
MultiPreference(arrayOf(stringResource(R.string.system),stringResource(R.string.light),stringResource(R.string.dark)), theme) { newTheme ->
theme = newTheme
prefs.edit { putString(keyTheme, newTheme) }
}
showGroupDialog = false
})
HorizontalDivider()
}
item {
Text(
"Map Projection",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
MultiPreference(arrayOf(stringResource(R.string.mercator), stringResource(R.string.azimuthalequidistant)), projection) { newProj ->
projection = newProj
prefs.edit { putString(keyProjection, newProj) }
Settings.refreshProjection()
}
HorizontalDivider()
}
item {
Text(
"Groups",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
var showDialog by remember{mutableStateOf(false)}
if(showDialog){
EditPlaceColorDialog(
deleteMode = true,
onDismiss = {
val g = Data.selected_group
if (g != null)
Data.visits.reassignAllVisitedToGroup(g.key)
showDialog = false
})
}
MultiPreference(
arrayOf(stringResource(R.string.on), stringResource(R.string.off)),
groups
) { key ->
if (key == offString) {
showDialog=true
}
groups = key
prefs.edit { putString(keyGroup, key) }
}
HorizontalDivider()
}
item {
Text(
text = "Regional",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier
.padding(top = 16.dp)
.clickable(onClick = {}),
)
RegionalScreen()
HorizontalDivider()
}
item{
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = { uri -> Data.doImport(context, uri) }
)
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(onClick = {
launcher.launch(arrayOf("*/*"))
}, modifier = Modifier
.fillMaxWidth(fraction = 0.4f)
.padding(vertical = 8.dp)) {
Text("Import")
}
Spacer(
modifier = Modifier.fillMaxWidth(0.4f)
)
Button(onClick = {
Data.doExport(context)
}, modifier = Modifier
.fillMaxWidth(fraction = 1f)
.padding(vertical = 8.dp)) {
Text("Export")
}
}
HorizontalDivider()
}
item {
PreferenceButton("Licenses") {
if (navController.currentDestination?.route != "licenses")
navController.navigate("licenses")
}
PreferenceButton("About") {
if (navController.currentDestination?.route != "about")
navController.navigate("about")
}
}
}
}
if (showRegionalDialog) {
@Composable
fun RegionalScreen() {
val context = LocalContext.current
val keyRegional = stringResource(R.string.key_regional)
val offString = stringResource(R.string.off)
val onString = stringResource(R.string.on)
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
var selected by remember { mutableStateOf(prefs.getString(keyRegional, offString)!!)}
var regional by remember{ mutableStateOf(prefs.getString(keyRegional, offString)!!)}
var showDialog by remember{mutableStateOf(false)}
var showLoad by remember{mutableStateOf(false)}
if(showDialog)
Dialog(
content = {
Column(
@@ -196,27 +308,26 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) {
MaterialTheme.colors.background,
RoundedCornerShape(corner = CornerSize(16.dp))
)
.padding(16.dp),
) {
Text(
style = MaterialTheme.typography.caption,
text = stringResource(R.string.delete_regions)
)
.padding(16.dp),){
Text(style=MaterialTheme.typography.caption, text= stringResource(R.string.delete_regions))
Button(onClick = {
GeoLocImporter.clearStates()
regional = offString
prefs.edit { putString(keyRegional, offString) }
showRegionalDialog = false
}) {
Text(stringResource(R.string.ok))
regional= selected
prefs.edit {
putString(
keyRegional,
regional
)
}
showDialog=false
}){
Text(stringResource(R.string.ok))
}
}
},
onDismissRequest = { showRegionalDialog = false }
onDismissRequest = { showDialog=false }
)
}
if (showLoad) {
if(showLoad){
Dialog(
content = {
CircularProgressIndicator(
@@ -228,155 +339,41 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) {
onDismissRequest = {}
)
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.background(MaterialTheme.colors.background)
) {
item {
Text(
stringResource(R.string.key_theme),
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
)
MultiPreference(
arrayOf(
stringResource(R.string.system),
stringResource(R.string.light),
stringResource(R.string.dark)
), theme
) { newTheme ->
theme = newTheme
prefs.edit { putString(keyTheme, newTheme) }
}
HorizontalDivider()
}
item {
Text(
stringResource(R.string.key_projection),
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
MultiPreference(
arrayOf(stringResource(R.string.mercator), stringResource(R.string.azimuthalequidistant)),
projection
) { newProj ->
projection = newProj
prefs.edit { putString(keyProjection, newProj) }
Settings.refreshProjection()
}
HorizontalDivider()
}
item {
SettingSwitch(
label = stringResource(R.string.key_group),
subtitle = stringResource(R.string.key_group_desc),
isChecked = groups == onString,
onCheckedChange = { isChecked ->
if (!isChecked) {
Data.selected_group = null
showGroupDialog = true
} else {
groups = onString
prefs.edit { putString(keyGroup, onString) }
val scope = rememberCoroutineScope()
MultiPreference(arrayOf(stringResource(R.string.on),stringResource(R.string.off)),regional) { key ->
when (key) {
offString -> { showDialog=true
selected=key
}
}
)
HorizontalDivider()
}
item {
SettingSwitch(
label = stringResource(R.string.key_regional),
subtitle = stringResource(R.string.key_regional_desc),
isChecked = regional == onString,
onCheckedChange = { isChecked ->
if (isChecked) {
regional = onString
prefs.edit { putString(keyRegional, onString) }
showLoad = true
onString -> {
regional = key
prefs.edit { putString(keyRegional, key) }
showLoad=true
scope.launch {
withContext(Dispatchers.IO) {
GeoLocImporter.importStates(context, true)
}
showLoad = false
}
} else {
showRegionalDialog = true
}
}
)
HorizontalDivider()
}
item {
SettingSwitch(
label = stringResource(R.string.pref_cascade_stats),
subtitle = stringResource(R.string.pref_cascade_stats_desc),
isChecked = cascadeStats == onString,
onCheckedChange = { isChecked ->
cascadeStats = if (isChecked) onString else offString
prefs.edit { putString(keyCascadeStats, cascadeStats) }
}
)
HorizontalDivider()
}
item {
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = { uri -> Data.doImport(context, uri) }
)
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
) {
Button(
onClick = {
launcher.launch(arrayOf("*/*"))
}, modifier = Modifier
.weight(1f)
) {
Text(stringResource(R.string.action_import))
}
Spacer(modifier = Modifier.size(16.dp))
Button(
onClick = {
Data.doExport(context)
}, modifier = Modifier
.weight(1f)
) {
Text(stringResource(R.string.action_export))
}
}
HorizontalDivider()
}
item {
PreferenceButton(stringResource(R.string.licenses)) {
if (navController.currentDestination?.route != "licenses")
navController.navigate("licenses")
}
PreferenceButton(stringResource(R.string.about)) {
if (navController.currentDestination?.route != "about")
navController.navigate("about")
}
}
}
}
@Composable
fun MultiPreference(list: Array<String>, selected: String, onSelected: (String) -> Unit) {
Column(Modifier.padding(vertical = 2.dp)) {
Column(Modifier.padding(2.dp)) {
list.forEach { value ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(42.dp)
.height(36.dp)
.clickable { onSelected(value) }) {
RadioButton(selected = selected == value, onClick = { onSelected(value) })
Text(
value, modifier = Modifier.padding(start = 12.dp),
style = MaterialTheme.typography.body1,
value, modifier = Modifier.padding(start = 8.dp),
color = MaterialTheme.colors.onBackground,
)
}
@@ -392,38 +389,3 @@ fun PreferenceButton(text: String, onClick: () -> Unit) {
Text(text)
}
}
@Composable
fun SettingSwitch(
label: String,
subtitle: String? = null,
isChecked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onCheckedChange(!isChecked) }
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text=label,
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
)
if (subtitle != null) {
Text(
text = subtitle,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
)
}
}
androidx.compose.material.Switch(
checked = isChecked,
onCheckedChange = onCheckedChange
)
}
}
@@ -20,7 +20,6 @@ import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@@ -33,15 +32,12 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import net.helcel.beans.R
import net.helcel.beans.countries.Country
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.countries.GeoLoc.LocType
import net.helcel.beans.countries.World
import net.helcel.beans.helper.AUTO_GROUP
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Groups
import net.helcel.beans.helper.NO_GROUP
import net.helcel.beans.helper.Settings
import net.helcel.beans.helper.Settings.isRegional
import net.helcel.beans.helper.Theme.getContrastColor
private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE)
@@ -50,7 +46,7 @@ private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE)
fun StatsScreen(
onExit: ()-> Unit
) {
val modes = if (Settings.isRegional(LocalContext.current)) MODE_LIST else MODE_LIST.take(2)
val modes = if (isRegional(LocalContext.current)) MODE_LIST else MODE_LIST.take(2)
var selectedTab by remember { mutableIntStateOf(0) }
var countMode by remember { mutableStateOf(true) }
@@ -61,7 +57,7 @@ fun StatsScreen(
TopAppBar(
title = {
Row(verticalAlignment = Alignment.CenterVertically){
Text(text=stringResource(R.string.action_stat), modifier = Modifier.weight(1f))
Text(text=stringResource(R.string.action_edit), modifier = Modifier.weight(1f))
Button(onClick = { countMode = !countMode }) {
Text(if (countMode) "Count" else "Area")
}
@@ -119,83 +115,26 @@ fun StatsList(activeMode: LocType, countMode: Boolean) {
@Composable
fun StatsRow(group: Groups.Group, activeMode: LocType, countMode: Boolean) {
val context = LocalContext.current
val isCascadeStats = remember { Settings.isCascadeStats(context) }
val visits by Data.visits.visitsFlow.collectAsState()
val continents = remember { World.WWW.children.toList() }
val countries = remember {
World.WWW.children.flatMap {
if (it is Country) listOf(it) else it.children
}
val visited = remember(group, activeMode) {
Data.visits.getVisitedByValue(group.key)
}
val visitedItems = remember(group, activeMode, isCascadeStats, visits) {
val list = when (activeMode) {
LocType.WORLD -> continents
LocType.COUNTRY -> countries
LocType.STATE -> countries.flatMap { it.children }
else -> emptyList()
}
fun getExplicitColor(l: GeoLoc): Int? {
val c = visits.getOrDefault(l.code, 0)
return if (c != NO_GROUP && c != AUTO_GROUP) c else null
}
fun getEffectiveGroup(l: GeoLoc): Int {
getExplicitColor(l)?.let { return it }
if (isCascadeStats) {
// Parent -> Child (closest explicit ancestor)
val country = countries.find { it.children.contains(l) }
val countryColor = country?.let { getExplicitColor(it) }
if (countryColor != null) return countryColor
val continent = continents.find {
it.children.contains(l) || (country != null && it.children.contains(country))
}
val continentColor = continent?.let { getExplicitColor(it) }
if (continentColor != null) return continentColor
val worldColor = getExplicitColor(World.WWW)
if (worldColor != null) return worldColor
// Child -> Parent (most common explicit descendant)
val descendants = mutableListOf<GeoLoc>()
fun collect(curr: GeoLoc) {
curr.children.forEach {
descendants.add(it)
collect(it)
}
}
collect(l)
val descendantColors = descendants.mapNotNull { getExplicitColor(it) }
if (descendantColors.isNotEmpty()) {
return descendantColors.groupBy { it }.maxByOrNull { it.value.size }?.key ?: AUTO_GROUP
}
} else {
if (l.type == LocType.WORLD || l.type == LocType.COUNTRY) {
val descendants = mutableListOf<GeoLoc>()
fun collect(curr: GeoLoc) {
curr.children.forEach { descendants.add(it); collect(it) }
}
collect(l)
val dc = descendants.mapNotNull { getExplicitColor(it) }
if (dc.isNotEmpty()) return dc.groupBy { it }.maxByOrNull { it.value.size }?.key ?: AUTO_GROUP
}
}
return AUTO_GROUP
}
list.filter { getEffectiveGroup(it) == group.key }
val count = when (activeMode) {
LocType.WORLD -> World.WWW.children.filter { it.code in visited }.size
LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.size
LocType.STATE -> World.WWW.children.flatMap { a->a.children.flatMap { b->b.children.filter { c->c.code in visited } } }.size
else -> 0
}
val count = visitedItems.size
val area = visitedItems.sumOf { it.area.toLong() }
val area = when (activeMode) {
LocType.WORLD -> World.WWW.children.filter { it.code in visited }.sumOf { it.area }
LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.sumOf { it.area }
LocType.STATE -> World.WWW.children.flatMap { a->a.children.flatMap { b->b.children.filter { c->c.code in visited } } }.sumOf { it.area }
else -> 0
}
val displayValue = if (countMode) count.toString() else stringResource(R.string.number_with_unit, area.toInt(), "km²")
val displayValue = if (countMode) count.toString() else stringResource(R.string.number_with_unit, area, "km²")
val backgroundColor = group.color.color
val textColor = getContrastColor(backgroundColor)
@@ -54,51 +54,27 @@ fun EditPlaceScreenPreview(){
EditPlaceScreen(Group.EEE)
}
fun syncVisited(loc: GeoLoc?=World.WWW): Boolean {
var changed = false
fun syncVisited(loc: GeoLoc?=World.WWW){
loc?.children?.forEach { tt ->
tt.children.forEach {itc->
if(Data.visits.getVisited(itc) in listOf(AUTO_GROUP,NO_GROUP)) {
val newState = if(itc.children.any { c -> Data.visits.getVisited(c) != NO_GROUP })
AUTO_GROUP
if(itc.children.any { c -> Data.visits.getVisited(c) != NO_GROUP })
Data.visits.setVisited(itc, AUTO_GROUP)
else
NO_GROUP
if (Data.visits.getVisited(itc) != newState) {
Data.visits.setVisited(itc, newState)
changed = true
}
Data.visits.setVisited(itc, NO_GROUP)
}
}
if(Data.visits.getVisited(tt) in listOf(AUTO_GROUP,NO_GROUP)) {
val newState = if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP })
AUTO_GROUP
if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP })
Data.visits.setVisited(tt, AUTO_GROUP)
else
NO_GROUP
if (Data.visits.getVisited(tt) != newState) {
Data.visits.setVisited(tt, newState)
changed = true
}
Data.visits.setVisited(tt, NO_GROUP)
}
}
// 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
fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
val visits by Data.visits.visitsFlow.collectAsState()
var showEdit by remember { mutableStateOf(false) }
val tabs : SnapshotStateList<GeoLoc> = remember { mutableStateListOf(loc) }
val ctx = LocalContext.current
@@ -108,12 +84,7 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
selectedTab = tabs.lastIndex
}
SideEffect {
// visits is used to trigger sync whenever data changes
if (visits.isNotEmpty() || true) {
if (syncVisited()) {
Data.saveData()
}
}
syncVisited()
}
BackHandler {
if (tabs.size > 1) tabs.removeAt(tabs.lastIndex)
@@ -124,7 +95,6 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
showEdit = false
if (it) {
Data.visits.setVisited(Data.selected_geoloc, NO_GROUP)
syncVisited()
Data.saveData()
if (Data.selected_geoloc!=null && Data.selected_geoloc!!.children.any { itc-> Data.visits.getVisited(itc) != NO_GROUP }) {
@@ -133,7 +103,6 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
}
if (Data.selected_group != null && Data.selected_geoloc != null) {
Data.visits.setVisited(Data.selected_geoloc, Data.selected_group!!.key)
syncVisited()
Data.saveData()
}
Data.selected_geoloc = null
@@ -175,14 +144,12 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
Data.visits.getVisited(itc)!= NO_GROUP } == true) AUTO_GROUP
else NO_GROUP
)
Data.saveData()
Data.selected_group = null
} else {
Data.selected_group = null
showEdit=true
}
syncVisited()
Data.saveData()
})
}
@@ -43,14 +43,14 @@ object Data {
if (groups.size() == 0) {
groups.setGroup(DEFAULT_GROUP, "Visited",
ContextCompat.getColor(ctx, R.color.blue).toDrawable())
saveData()
}
saveData()
}
fun saveData() {
if(groups.id != visits.id) return
val id = groups.id
sharedPreferences.edit(commit=true) {
sharedPreferences.edit {
putString("groups_$id", groupsSerial.writeTo(groups))
putString("visits_$id", visitsSerial.writeTo(visits))
}
@@ -3,7 +3,6 @@ package net.helcel.beans.helper
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.json.Json
@@ -25,32 +24,19 @@ const val AUTO_GROUP = -1
@Serializable
class Groups(val id: Int, @SerialName("grps") private val groups: HashMap<Int, Group>) {
class Groups(val id: Int, private val groups: HashMap<Int, Group>) {
@kotlinx.serialization.Transient
private val _groupsFlow = MutableStateFlow<List<Group>>(groups.values.toList())
@kotlinx.serialization.Transient
val groupsFlow: StateFlow<List<Group>> = _groupsFlow.asStateFlow()
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)
updateFlow()
_groupsFlow.value = groups.values.toList()
}
fun deleteGroup(key: Int) {
groups.remove(key)
updateFlow()
}
fun keepOnly(key: Int) {
val keep = groups[key] ?: return
groups.clear()
groups[key] = keep
updateFlow()
}
private fun updateFlow() {
_groupsFlow.value = groups.values.toList()
}
@@ -29,13 +29,6 @@ object Settings {
)
}
fun isCascadeStats(ctx: Context): Boolean {
return getBooleanValue(
ctx,
sp.getString(ctx.getString(R.string.key_cascade_stats), ctx.getString(R.string.off))
)
}
fun getStatPref(ctx: Context): String? {
return sp.getString(
ctx.getString(R.string.key_stats),
@@ -19,25 +19,24 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) {
val visitsFlow: StateFlow<Map<String,Int>> = _visitsFlow
fun setVisited(key: GeoLoc?, b: Int) {
if (key == null || locs[key.code] == b)
if (key == null)
return
_visitsFlow.value = _visitsFlow.value.toMutableMap().apply {
this[key.code] = b
}
locs[key.code] = b
updateFlow()
}
private fun updateFlow() {
_visitsFlow.value = locs.toMap()
}
fun deleteVisited(key: Int) {
val keysToDelete = locs
.filter { it.value == key }
.map { it.key }
if (keysToDelete.isEmpty()) return
_visitsFlow.value = _visitsFlow.value.toMutableMap().apply {
keysToDelete.forEach { this.remove(it)}
}
keysToDelete.forEach {
locs.remove(it)
}
updateFlow()
}
fun getVisited(key: GeoLoc): Int {
@@ -61,19 +60,13 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) {
}
fun reassignAllVisitedToGroup(group: Int) {
var changed = false
val keys = locs.filter { (_, grp) ->
grp !in listOf(NO_GROUP, AUTO_GROUP)
}.keys
keys.forEach {
if (locs[it] != group) {
locs[it] = group
changed = true
}
}
if (changed) {
updateFlow()
locs[it] = group
}
_visitsFlow.value = locs
}
@OptIn(ExperimentalSerializationApi::class)
@@ -1,49 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group android:scaleX="0.8918919"
android:scaleY="0.8918919"
android:translateX="6.918919"
android:translateY="6.918919">
<group
android:scaleX="1.5"
android:scaleY="1.5"
android:translateX="10"
android:translateY="10">
<path
android:pathData="M36,36m-28,0a28,28 0,1 1,56 0a28,28 0,1 1,-56 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
<group
android:scaleX="1.25"
android:scaleY="1.25"
android:translateX="18"
android:translateY="18">
<path
android:pathData="M56.803,45.75c0.599,-0.299 1.157,-0.672 1.663,-1.11 0.804,-0.698 1.463,-1.546 1.94,-2.497 0.599,-1.303 0.883,-2.728 0.829,-4.161 0.034,-1.498 -0.154,-2.994 -0.558,-4.437 -0.451,-1.645 -1.104,-3.229 -1.944,-4.714 -0.967,-2.141 -2.28,-4.108 -3.887,-5.822 -1.242,-1.326 -2.757,-2.366 -4.44,-3.048 -0.877,-0.372 -1.821,-0.56 -2.774,-0.553 -1.402,0.074 -2.729,0.697 -3.882,1.389 -1.285,0.81 -2.244,1.735 -2.771,3.053 -0.739,2.362 0.827,4.821 1.113,5.269 0.003,0.005 0.187,0.281 0.555,0.832 1.512,2.264 1.589,2.358 1.666,2.495 0.646,1.221 1.114,2.528 1.389,3.882 1.043,4.001 1.565,6.001 2.223,6.932 1.208,1.71 3.455,3.414 5.826,3.325 1.059,-0.088 2.093,-0.371 3.05,-0.835Z"
android:strokeWidth="2"
android:fillColor="@color/white"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M22,41c-1.54,0.554 -2.83,1.642 -3.636,3.066 -0.714,1.365 -0.957,2.928 -0.693,4.445 0.178,1.247 0.632,2.439 1.329,3.488 2.032,3.228 5.383,4.423 7,5 1.613,0.561 3.295,0.897 5,1 2.387,0.205 6.923,0.535 11,-2 1.287,-0.665 2.335,-1.713 3,-3 0.661,-1.601 0.661,-3.399 0,-5 -0.389,-1.156 -1.121,-2.165 -2.099,-2.894 -0.919,-0.599 -1.974,-0.956 -3.069,-1.039 -2.057,-0.313 -2.756,0.139 -5.014,0.057 -1.271,-0.019 -2.534,-0.214 -3.753,-0.577 -3.036,-0.95 -3.33,-2.457 -5.889,-2.829 -1.066,-0.158 -2.155,-0.061 -3.177,0.282Z"
android:strokeWidth="2"
android:fillColor="@color/white"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M12.7,26.975c1.758,-3.604 5.311,-3.191 9.58,-7.091 2.701,-2.469 2.858,-4.078 5.613,-5.202 1.407,-0.644 2.984,-0.819 4.498,-0.5 2.053,0.475 3.761,1.891 4.61,3.819 1.116,2.742 -0.314,5.437 -1.398,7.482 -0.951,1.689 -2.167,3.215 -3.602,4.518 -1.479,1.552 -3.161,2.898 -5,4 -2.629,1.541 -6.332,3.711 -9.989,2.43 -1.481,-0.548 -2.768,-1.521 -3.699,-2.796 -1.496,-1.906 -1.735,-4.512 -0.612,-6.659Z"
android:strokeWidth="2"
android:fillColor="@color/white"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
</group>
</vector>
@@ -2,5 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
-15
View File
@@ -1,15 +0,0 @@
<?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
View File
@@ -1,7 +0,0 @@
<?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>
+1 -14
View File
@@ -15,21 +15,8 @@
<string name="percentages">Prefer percentages (%)</string>
<string name="licenses">Licenses</string>
<string name="key_group">Groups</string>
<string name="key_regional">Regional mode</string>
<string name="key_regional_desc">Enable tracking of states and provinces</string>
<string name="key_cascade_stats">key_cascade_stats</string>
<string name="pref_cascade_stats">Cascade statistics</string>
<string name="pref_cascade_stats_desc">Include parents and children in the counts</string>
<string name="key_group_desc">Enable multiple colors</string>
<string name="key_regional">Regional</string>
<string name="about">About</string>
<string name="pref_category_appearance">Appearance</string>
<string name="pref_category_features">Features</string>
<string name="pref_category_data">Data</string>
<string name="action_import">Import</string>
<string name="action_export">Export</string>
<string name="import_desc">Load data from a previously exported file.</string>
<string name="export_desc">Save your current data to a file for backup or transfer.</string>
<string name="key_projection_desc">Select how the map is projected onto a 2D surface.</string>
<string name="beans_is_foss">Beans is free and open source software, licensed under the GNU General Public License (version 3 or later)</string>
<string name="beans_repo_uri">https://github.com/helcel-net/beans</string>
<string name="beans_repo">Project repository: %1$s\n Feel free to report issues or contribute.</string>
-16
View File
@@ -1,16 +0,0 @@
<?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>
+1 -1
View File
@@ -2,5 +2,5 @@
plugins {
id 'com.android.application' version '9.2.1' apply false
id 'com.android.library' version '9.2.1' apply false
id 'org.jetbrains.kotlin.android' version '2.4.0' apply false
id 'org.jetbrains.kotlin.android' version '2.3.21' apply false
}
+8 -52
View File
@@ -1,10 +1,8 @@
#!/bin/node
import {readFileSync,writeFileSync,existsSync} from 'fs'
import {readFileSync, existsSync} from 'fs'
import area from '@turf/area'
import * as turf from '@turf/turf'
import * as path from 'path';
import { JSDOM } from 'jsdom';
const countries =
@@ -26,7 +24,7 @@ const countries =
const groups = {
"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"],
"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"],
"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"],
"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"],
@@ -34,6 +32,7 @@ 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"],
"XXX":[
"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
"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
@@ -41,11 +40,16 @@ const groups = {
"CCK",// Cocos (Keeling) Islands: an Australian external territory in the Indian Ocean
"FRO",// Faroe Islands: an autonomous region of Denmark
"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
"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
"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
"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
"VIR",// United States Virgin Islands: an unincorporated territory of the United States in the Caribbean
]
@@ -162,52 +166,4 @@ 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()
fixSvg("./app/src/main/assets/loxim01.svg")
fixSvg("./app/src/main/assets/webmercator01.svg")
fixSvg("./app/src/main/assets/aeqd01.svg")
+5 -49
View File
@@ -8,11 +8,10 @@ GADM_BASEPATH="https://geodata.ucdavis.edu/gadm"
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"
# Caspian Sea: "XCA"
countries=(
"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"
"CMR" "CAN" "CPV" "CYM" "CAF" "TCD" "CHL" "CHN" "CXR" "XCL" "CCK" "COL" "COM" "COK" "CRI" "CIV" "HRV" "CUB" "CUW" "CYP" "CZE" "COD"
"CMR" "CAN" "CPV" "XCA" "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"
"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"
@@ -116,47 +115,6 @@ 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
}
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() {
input_files=()
@@ -178,9 +136,9 @@ toSVG_01() {
fi
done
generate_svg_map "./app/src/main/assets/loxim01.svg" "loxim" "${input_files[@]}"
generate_svg_map "./app/src/main/assets/webmercator01.svg" "webmercator" "${input_files[@]}"
generate_svg_map "./app/src/main/assets/aeqd01.svg" "aeqd +lat_0=90" "${input_files[@]}"
"$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
"$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
"$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
}
do_1() {
@@ -195,12 +153,10 @@ do_0() {
do
download_0 "$country"
done
wget -q -O "./temp/0/ATA.json" "$ATA_URL"
wget -q -O "./temp/1/ATA.json" "$ATA_URL"
}
# do_0
# do_1
# toSVG_0
# toSVG_1
toSVG_01
#CUW, CCK, XCL, CXR, IOT, BVT, ABW, FLK, GIB, HMD, KIR, SXM, MDV, MCO, NIU, NFK, PCN, MAF, SGS, VAT
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Vendored
+2 -2
View File
@@ -20,7 +20,7 @@
##############################################################################
#
# gradlew start up script for POSIX generated by Gradle.
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh gradlew
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
Vendored
+2 -2
View File
@@ -19,7 +19,7 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem gradlew startup script for Windows
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@@ -72,7 +72,7 @@ echo location of your Java installation. 1>&2
@rem Execute gradlew
@rem Execute Gradle
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
+1 -1
View File
@@ -3,7 +3,7 @@
"@turf/area": "^7.3.5",
"@turf/turf": "^7.3.5",
"jsdom": "^29.1.1",
"mapshaper": "^0.7.22"
"mapshaper": "^0.7.19"
},
"type": "module"
}
+326 -331
View File
File diff suppressed because it is too large Load Diff