diff --git a/src/core/admin.py b/src/core/admin.py
index 216cefcfa5c5cd90120f02881de53950b16d0b83..c00e2f344b16a5603d58bd58a4a1ca6cb4c48b4d 100644
--- a/src/core/admin.py
+++ b/src/core/admin.py
@@ -378,9 +378,9 @@ class ActivityLogEntryInline(GenericTabularInline):
 
 
 class AssemblyAdmin(GISModelAdmin):
-    list_display = ['conference', 'parent', 'slug', 'name', 'state', 'is_official']
+    list_display = ['conference', 'parent', 'slug', 'name', 'state', 'location_state', 'is_official']
     list_display_links = ['slug', 'name']
-    list_filter = ['conference', 'parent', 'state', 'is_official']
+    list_filter = ['conference', 'parent', 'state', 'location_state', 'is_official']
     readonly_fields = ['id', 'conference']
     search_fields = ['slug', 'name', 'description']
     inlines = [TagsInline, BadgeInline, AssemblyLinkInline, AssemblyMemberInline, ActivityLogEntryInline]
@@ -407,7 +407,7 @@ class AssemblyAdmin(GISModelAdmin):
         (
             'Location',
             {
-                'fields': ['assembly_location', 'location_point', 'location_boundaries'],
+                'fields': ['location_state', 'assembly_location', 'location_point', 'location_boundaries', 'location_data'],
             },
         ),
     )
diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po
index aee8a920424d3546646e892efd7a84b515d2d475..54605f17f955c3b137a75835b2e6c296b318595e 100644
--- a/src/core/locale/de/LC_MESSAGES/django.po
+++ b/src/core/locale/de/LC_MESSAGES/django.po
@@ -281,6 +281,21 @@ msgstr "Habitat"
 msgid "Assembly__hierarchy__cluster_restricted"
 msgstr "Habitat (nicht öffentlich)"
 
+msgid "Assembly__location_state-none"
+msgstr "nicht verfügbar"
+
+msgid "Assembly__location_state-draft"
+msgstr "Entwurf/Idee (Orga only)"
+
+msgid "Assembly__location_state-rough"
+msgstr "grob (intern)"
+
+msgid "Assembly__location_state-preview"
+msgstr "grob (öffentliche Vorschau)"
+
+msgid "Assembly__location_state-final"
+msgstr "final (öffentlich)"
+
 msgid "Assembly__slug__help"
 msgstr "Name der Assembly für URLs, bestehend aus Buchstaben, Nummern, Unterstrichen und Bindestrichen"
 
@@ -381,6 +396,12 @@ msgstr "öffentlich sichtbare E-Mail für Kontakt"
 msgid "Assembly__public_contact"
 msgstr "öffentlicher Kontakt"
 
+msgid "Assembly__location_state__help"
+msgstr "Qualität der ausgewählten Position auf der Karte"
+
+msgid "Assembly__location_state"
+msgstr "Position (Karte) Status"
+
 msgid "Assembly__location_floor__help"
 msgstr "Ebene, auf der sich die Assembly befindet"
 
diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po
index b66a8888e0460b2825bbb7051250afd0b57cd999..d5c6b5e3d29d407e90a308af459a86bdf22ae8f1 100644
--- a/src/core/locale/en/LC_MESSAGES/django.po
+++ b/src/core/locale/en/LC_MESSAGES/django.po
@@ -281,6 +281,21 @@ msgstr "habitat"
 msgid "Assembly__hierarchy__cluster_restricted"
 msgstr "habitat (non-public)"
 
+msgid "Assembly__location_state-none"
+msgstr "not available"
+
+msgid "Assembly__location_state-draft"
+msgstr "draft (orga only)"
+
+msgid "Assembly__location_state-rough"
+msgstr "rough (internal)"
+
+msgid "Assembly__location_state-preview"
+msgstr "rough (public preview)"
+
+msgid "Assembly__location_state-final"
+msgstr "final (public)"
+
 msgid "Assembly__slug__help"
 msgstr "short name of this assembly used in URLs, consisting of letters, numbers, underscores and hyphens"
 
@@ -381,6 +396,12 @@ msgstr "publicly visible email for contact"
 msgid "Assembly__public_contact"
 msgstr "public contact"
 
+msgid "Assembly__location_state__help"
+msgstr "quality of the selected position, how to handle it?"
+
+msgid "Assembly__location_state"
+msgstr "location state"
+
 msgid "Assembly__location_floor__help"
 msgstr "floor, on which this assembly is placed"
 
diff --git a/src/core/migrations/0172_assembly_location_state.py b/src/core/migrations/0172_assembly_location_state.py
new file mode 100644
index 0000000000000000000000000000000000000000..1fc3d11690572d52ecf2c7539c9af149dccedfa5
--- /dev/null
+++ b/src/core/migrations/0172_assembly_location_state.py
@@ -0,0 +1,32 @@
+# Generated by Django 5.1.3 on 2024-12-23 11:13
+
+from django.db import migrations, models
+
+
+def setup_location_state(apps, schema_editor):
+    Assembly = apps.get_model("core", "Assembly")  # type=core.models.Assembly
+    for a in Assembly.objects.all():
+        if a.location_data:
+            if a.state in ['placed', 'arrived', 'confirmed']:  # aka a.is_placed
+                a.location_state = 'final' if a.state == 'placed' else 'preview'
+            else:
+                a.location_state = 'draft'
+        else:
+            a.location_state = 'none'
+        a.save(update_fields=["location_state"])
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0171_remove_team_require_staff_team_visibility'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='assembly',
+            name='location_state',
+            field=models.CharField(choices=[('none', 'Assembly__location_state-none'), ('draft', 'Assembly__location_state-draft'), ('rough', 'Assembly__location_state-rough'), ('preview', 'Assembly__location_state-preview'), ('final', 'Assembly__location_state-final')], default='none', help_text='Assembly__location_state__help', max_length=20, verbose_name='Assembly__location_state'),
+        ),
+        migrations.RunPython(setup_location_state, migrations.RunPython.noop, elidable=True),
+    ]
diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py
index 292948f540dcc0fbc5893ba0d4ba51281dde8625..1a342cb69ba17ae1d3b0474523b1f81015047a1a 100644
--- a/src/core/models/assemblies.py
+++ b/src/core/models/assemblies.py
@@ -107,6 +107,18 @@ class Assembly(TaggedItemMixin, ActivityLogMixin, RulesModel):
         CLUSTER = 'cluster', _('Assembly__hierarchy__cluster')
         CLUSTER_RESTRICTED = 'clusterrestricted', _('Assembly__hierarchy__cluster_restricted')
 
+    class LocationState(models.TextChoices):
+        NONE = 'none', _('Assembly__location_state-none')
+        """No location set."""
+        DRAFT = 'draft', _('Assembly__location_state-draft')
+        """Internal preview, i.e. assembly orga only."""
+        ROUGH = 'rough', _('Assembly__location_state-rough')
+        """Rough location idea, hub-internal only."""
+        PREVIEW = 'preview', _('Assembly__location_state-preview')
+        """Rough location idea, shared with integrations like c3nav."""
+        FINAL = 'final', _('Assembly__location_state-final')
+        """Precise location data, shared with the world."""
+
     objects = AssemblyManager()
     logger = logging.getLogger(__name__)
 
@@ -201,6 +213,13 @@ class Assembly(TaggedItemMixin, ActivityLogMixin, RulesModel):
         related_query_name='assemblies_invited',
     )
 
+    location_state = models.CharField(
+        max_length=20,
+        choices=LocationState.choices,
+        default=LocationState.NONE,
+        help_text=_('Assembly__location_state__help'),
+        verbose_name=_('Assembly__location_state'),
+    )
     location_floor = models.ForeignKey(
         MapFloor,
         blank=True,