Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
442cc99
consolidate all markdown pages into simpler structure
mradamcox Dec 30, 2023
e74f43d
replace fullscreen with fake fullscreen
mradamcox Dec 30, 2023
cb48613
small modal updates
mradamcox Dec 30, 2023
23666c0
initial creation of better place search dropdowns
mradamcox Dec 30, 2023
2720062
switch to display name, add link
mradamcox Jan 2, 2024
729a737
better cursor style
mradamcox Jan 2, 2024
f7f56c0
fix syntax
mradamcox Jan 2, 2024
f0f22cc
wrap selects
mradamcox Jan 2, 2024
afd9056
add username validator #166
mradamcox Jan 4, 2024
ec941b5
remove ounused methods
mradamcox Jan 5, 2024
68d56db
refactor select list creation to model
mradamcox Jan 5, 2024
5031652
move Viewer view to frontend app
mradamcox Jan 5, 2024
34bf9a2
improve place navigation by hierarchy
mradamcox Jan 6, 2024
4e95bb6
fix method name
mradamcox Jan 6, 2024
51d3e22
add extra check for lyr ct
mradamcox Jan 6, 2024
ccecf68
further item api updates, esp. within place context
mradamcox Jan 6, 2024
2473faf
add font back in
mradamcox Jan 6, 2024
4425eca
include current locale with descendants
mradamcox Jan 6, 2024
81e0c4d
overflow style
mradamcox Jan 6, 2024
2746a7b
use icons for arrows
mradamcox Jan 9, 2024
3e4f82e
refactor nav bar
mradamcox Jan 9, 2024
a061e8c
small text/style updates
mradamcox Jan 9, 2024
42f52cc
use avatar in navbar
mradamcox Jan 11, 2024
541777b
move resource view to content app
mradamcox Jan 12, 2024
9227014
refactor context processor; remove console log
mradamcox Jan 13, 2024
19408be
move volume view to content app
mradamcox Jan 13, 2024
41639af
refactor some modals
mradamcox Jan 15, 2024
a7d8d5f
initial creation of volume permissions
mradamcox Jan 17, 2024
e5e2a93
implement import aliases
mradamcox Jan 17, 2024
2b5b3ec
big refactor to begin unifying components
mradamcox Jan 17, 2024
beb1fd1
add more icons
mradamcox Jan 18, 2024
9f7d65d
reorganize all svelte content for better structure
mradamcox Jan 22, 2024
cd18ea1
remove temp contact form
mradamcox Jan 23, 2024
6aca11a
add download section modal; Link component
mradamcox Jan 25, 2024
a1b363a
significant work on overview pages
mradamcox Jan 30, 2024
86e39ef
increase thumbnail size to support title page display
mradamcox Feb 5, 2024
4bac1de
refactor svg icons
mradamcox Feb 5, 2024
3abfc12
refactor overview pages, title bar, and signin form
mradamcox Feb 5, 2024
ffcb2db
cleanup, fix download file names
mradamcox Feb 6, 2024
57ac3bc
hide load button from anonymous users
mradamcox Feb 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions ohmg/accounts/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.contrib.auth.validators import UnicodeUsernameValidator

class OHMGUnicodeUsernameValidator(UnicodeUsernameValidator):
"""
Slight change to UnicodeUsernameValidator so that it doesn't allow @.
"""
regex = r'^[\w.+-]+\Z'
message = "Username may contain only letters, numbers, and . + - _"

custom_username_validators = [
OHMGUnicodeUsernameValidator(),
]
44 changes: 37 additions & 7 deletions ohmg/api/api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import logging
from typing import List
from typing import List, Optional

from django.conf import settings
from django.shortcuts import get_object_or_404
from django.urls import reverse

from ninja import NinjaAPI, Query
from ninja import NinjaAPI, Query, FilterSchema
from ninja.pagination import paginate
from ninja.security import APIKeyHeader

Expand Down Expand Up @@ -65,15 +65,45 @@ def list_users(request):
queryset = User.objects.all().exclude(username="AnonymousUser").order_by("username")
return list(queryset)

class ItemFilterSchema(FilterSchema):
"""not currently used, would need to customize the fields a bit"""
loaded_by__username: Optional[str] = None

@api.get('items/', response=List[ItemListSchema], url_name="item_list")
def list_items(request, sort: str = "default", limit: int = None):
def list_items(request,
# filters: ItemFilterSchema = Query(...),
sort: str = "default",
limit: int = None,
loaded: bool = True,
loaded_by: str = None,
locale: str = None,
locale_inclusive: bool = False,
):
# overall, not really optimized. should refactor at some point...
if sort == "load_date":
queryset = Volume.objects.all().exclude(loaded_by=None).order_by('-load_date')
items = Volume.objects.all().order_by('-load_date')
else:
queryset = Volume.objects.all().exclude(loaded_by=None).order_by('city', 'year')
items = Volume.objects.all().order_by('city', 'year')

if locale:
place = Place.objects.get(slug=locale)
if locale_inclusive:
if locale in ['united-states', 'mexico', 'canada', 'cuba']:
pass
else:
pks = place.get_inclusive_pks()
items = items.filter(locales__in=pks)
else:
items = items.filter(locales=place.pk)

if loaded:
items = items.exclude(loaded_by=None)
if loaded_by:
items = items.filter(loaded_by__username=loaded_by)

if limit:
queryset = queryset[:limit]
return list(queryset)
items = items[:limit]
return list(items)

@api.get('places/', response=List[PlaceSchema], url_name="place_list")
def list_places(request):
Expand Down
24 changes: 0 additions & 24 deletions ohmg/content/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,27 +271,3 @@ def generate_mosaic_json(self, trim_all=False):

logger.info(f"{self.vol.identifier} | mosaic created: {os.path.basename(mosaic_json_path)}")
return mosaic_json_path

def generate_mosaic_jpg(self, out_path):
''' Create a non-geo JPEG version of the COG mosaic. Still a work in progress...

Ultimately, would like for this JPEG to not have internal overviews, but I haven't
had any luck removing them during the conversion.

Tried using PIL instead of gdal, but still getting the overviews. PIL can seek "frames"
in the image, and different overviews ARE different frames, but the base data still
seems to include all of the upper overviews!
'''

mosaic_vrt = self.generate_mosaic_vrt()

to = gdal.TranslateOptions(
format="GTiff",
creationOptions = [
"COMPRESS=JPEG",
"BIGTIFF=YES",
#"INTERNAL_MASK=NO",
],
)

gdal.Translate(out_path, mosaic_vrt, options=to)
2 changes: 1 addition & 1 deletion ohmg/content/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def resolve_stats(obj):
if obj.multimask is not None:
mm_ct = len(obj.multimask)
mm_todo = main_lyrs_ct - mm_ct
if mm_ct > 0:
if mm_ct > 0 and main_lyrs_ct > 0:
mm_display = f"{mm_ct}/{main_lyrs_ct}"
mm_percent = mm_ct / main_lyrs_ct
mm_percent += main_lyrs_ct * .000001
Expand Down
11 changes: 11 additions & 0 deletions ohmg/content/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.urls import path

from .views import (
ItemView,
VirtualResourceView,
)

urlpatterns = [
path('item/<str:identifier>', ItemView.as_view(), name="resource_detail"),
path('resource/<int:pk>', VirtualResourceView.as_view(), name="resource_detail"),
]
124 changes: 124 additions & 0 deletions ohmg/content/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import json
import logging
from datetime import datetime

from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import render, get_object_or_404
from django.views import View
from django.middleware import csrf
from django.urls import reverse

from ohmg.georeference.models import (
Layer,
Document,
ItemBase,
)
from ohmg.loc_insurancemaps.models import Volume, find_volume
from ohmg.loc_insurancemaps.tasks import load_docs_as_task
from ohmg.frontend.context_processors import user_info_from_request

logger = logging.getLogger(__name__)


class ItemView(View):

def get(self, request, volumeid):

volume = get_object_or_404(Volume, pk=volumeid)
volume_json = volume.serialize(include_session_info=True)

context_dict = {
"svelte_params": {
"TITILER_HOST": settings.TITILER_HOST,
"VOLUME": volume_json,
"CSRFTOKEN": csrf.get_token(request),
"USER": user_info_from_request(request),
"MAPBOX_API_KEY": settings.MAPBOX_API_TOKEN,
}
}
return render(
request,
"content/item.html",
context=context_dict
)

def post(self, request, volumeid):

body = json.loads(request.body)
operation = body.get("operation", None)

if operation == "initialize":
volume = Volume.objects.get(pk=volumeid)
if volume.loaded_by is None:
volume.loaded_by = request.user
volume.load_date = datetime.now()
volume.save(update_fields=["loaded_by", "load_date"])
load_docs_as_task.delay(volumeid)
volume_json = volume.serialize(include_session_info=True)
volume_json["status"] = "initializing..."

return JsonResponse(volume_json)

elif operation == "set-index-layers":

volume = Volume.objects.get(pk=volumeid)

lcat_lookup = body.get("layerCategoryLookup", {})

for cat in volume.sorted_layers:
volume.sorted_layers[cat] = [k for k, v in lcat_lookup.items() if v == cat]

volume.save(update_fields=["sorted_layers"])
volume_json = volume.serialize(include_session_info=True)
return JsonResponse(volume_json)

elif operation == "refresh":
volume = Volume.objects.get(pk=volumeid)
volume_json = volume.serialize(include_session_info=True)
return JsonResponse(volume_json)

elif operation == "refresh-lookups":
volume = Volume.objects.get(pk=volumeid)
volume.refresh_lookups()
volume_json = volume.serialize(include_session_info=True)
return JsonResponse(volume_json)

class VirtualResourceView(View):

def get(self, request, pk):

resource = get_object_or_404(ItemBase, pk=pk)
if resource.type == 'document':
resource = Document.objects.get(pk=pk)
elif resource.type == 'layer':
resource = Layer.objects.get(pk=pk)

split_summary = resource.get_split_summary()
georeference_summary = resource.get_georeference_summary()
resource_json = resource.serialize()

volume = find_volume(resource)
volume_json = None
if volume is not None:
volume_json = volume.serialize()

return render(
request,
"content/resource.html",
context={
'resource_params': {
'REFRESH_URL': None,
'RESOURCE': resource_json,
'VOLUME': volume_json,
'CSRFTOKEN': csrf.get_token(request),
"USER": user_info_from_request(request),
"SPLIT_SUMMARY": split_summary,
"GEOREFERENCE_SUMMARY": georeference_summary,
"MAPBOX_API_KEY": settings.MAPBOX_API_TOKEN,
"OHMG_API_KEY": settings.OHMG_API_KEY,
"SESSION_API_URL": reverse("api-beta:session_list"),
"TITILER_HOST": settings.TITILER_HOST,
}
}
)
34 changes: 22 additions & 12 deletions ohmg/frontend/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
from django.conf import settings
from django.contrib.sites.models import Site
from ohmg.utils import full_reverse
from django.middleware import csrf

def loc_info(request):
from ohmg.accounts.schemas import UserSchema

def user_info_from_request(request):
""" Return a set of info for the current user in the request. """

try:
user = request.user
except AttributeError:
user = None
if user and user.is_authenticated:
user_info = {
'is_authenticated': True,
'name': user.username,
'profile': full_reverse("profile_detail", args=(user.username, )),
}
user_info = UserSchema.from_orm(user).dict()
user_info['is_authenticated'] = True
user_info['is_staff'] = user.is_staff
else:
user_info = {
'is_authenticated': False
'is_authenticated': False,
'is_staff': False,
}
return user_info

def navbar_footer_params(request):
""" Build the params passed to the Navbar and Footer Svelte components."""

return {
'navbar_params': {
'USER': user_info
}
'USER': user_info_from_request(request),
'CSRFTOKEN': csrf.get_token(request)
},
'footer_params': {},
}

def general(request):
""" neifnef """
def site_info(request):
""" Return site name, build number, etc. """

site = Site.objects.get_current()
return {
'SITE_NAME': site.name,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script>
import MarkdownPage from './components/MarkdownPage.svelte'

const source = `### In Brief...
---
page_title: About Sanborn maps
header: A bit about Sanborn maps
---

### In Brief...

The Sanborn Map Company surveyed and mapped American cities from the late 1860's through the 1950's, creating city atlases and selling them to insurance companies on a subscription basis. The extensive details they recorded for each building--commercial use, construction materials, and exact locations of heat sources (to name but a few)--provided insurance companies with the information they needed to geographically visualize and balance their risk.

Expand Down Expand Up @@ -43,7 +45,3 @@ For **much** more, see this Library of Congress [Sanborn Internet Resources](htt
When the Sanborn Map company originally published these maps, all content for a given city in a given year was released in a single edition. However, in large cities like New Orleans one edition may actually comprise multiple volumes, and in the Library of Congress collection each of these volumes is stored as a separate item.

Thus, on OldInsuranceMaps.net, **volume** is the highest level of grouping of historical maps. Each volume has one or more sheets, and when a user starts ("loads") a volume, its sheets are loaded in to the system to be processed indivitually.
`
</script>

<MarkdownPage source={source} title={"A bit about Sanborn maps"} />
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script>
import MarkdownPage from './components/MarkdownPage.svelte'
---
page_title: About
header: About this site...
---

const source = `
### Background

The first iteration of OldInsuranceMaps.net was made publicly available as LaHMG (Louisiana Historical Map Georeferencer) in early 2022 through a four-month pilot project, focusing on maps of Louisiana. This work formed the bulk my master's thesis at Louisiana State University: ["Creating a Public Space for Georeferencing Sanborn Maps: A Louisiana Case Study"](https://digitalcommons.lsu.edu/gradschool_theses/5641/).
Expand Down Expand Up @@ -43,8 +44,3 @@ Icons & Logo: [Alex Muravev](https://thenounproject.com/alex2900/) (via Noun Pro
All maps on this site are in the public domain, pulled from the Library of Congress [Sanborn Map Collection](https://loc.gov/collections/sanborn-maps).

All georeferencing work performed by [our contributors](/profiles). For each volume, a list of contributors appears at the bottom of the summary page.
`

</script>

<MarkdownPage source={source} title={"About this site..."}/>
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
<script>
import MarkdownPage from './components/MarkdownPage.svelte'

const source = `
<br>
---
page_title: Contact
header: Get in touch
---

Questions, concerns, or feedback: [hello@oldinsurancemaps.net](mailto:hello@oldinsurancemaps.net)

Bugs and technical discussion: [mradamcox/ohmg](https://github.com/mradamcox/ohmg)
`

</script>

<MarkdownPage source={source} title={"Contact"} makeToc={false}/>
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script>
import MarkdownPage from './components/MarkdownPage.svelte'

const source = `
---
page_title: FAQ
header: FAQ
---

### Why ".net"?

Because the idea is to create a collaborative, public place (a network or "commons") rather than a commercial product or non-profit organization.
Expand Down Expand Up @@ -45,7 +46,3 @@ If you or your organization would like to a large number of maps, like all your
We are using this term somewhat imprecisely, because only Sanborn maps of large cities have proper "volumes" (see New Orleans 1885 vol. 1, 1885 vol. 2, 1887 vol. 3, and 1893 vol. 4). The vast majority of maps in the Sanborn collection may be better called "editions" (see Baton Rouge, 1885).

However, the hierarchy of the LOC collection handles editions and volumes at the same level, they are "items", so we decided "volume" would be a reasonable compromise for the sake of unity. Therefore, a volume is an atomic unit that contains one or more sheets.
`
</script>

<MarkdownPage source={source} title={"FAQ"} />
Loading