Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an advanced search option to "display results in a map" #1754

Merged
merged 29 commits into from
Feb 9, 2024
Merged
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d235fd2
Add support for search engine document updates
ffont Jan 23, 2024
9e3b678
Small fixes in search test command
ffont Jan 23, 2024
300201e
Rename comments field to num_comments
ffont Jan 23, 2024
21349a9
Make fields non-required, some cleanups
ffont Jan 23, 2024
26991be
Add tests for update/fields_to_include parameters of add_sounds_to_index
ffont Jan 23, 2024
92ea90e
Add solr-based basic similarity search support
ffont Jan 23, 2024
0d793d1
Add more taks to vscode workspace
ffont Jan 24, 2024
edddb73
Replace docker-compose by docker compose in docs
ffont Jan 24, 2024
475a222
Add parameter to chose analyzer for similarity
ffont Jan 24, 2024
442b3a3
Handle case with no valid embeddings
ffont Jan 24, 2024
85fd8d2
Get appropriate similairty state when search engine similarity is ena…
ffont Jan 24, 2024
fc09d38
Document similar_to new params in search_sound
ffont Jan 24, 2024
5233d6a
Add get param to test using a sound as target in search page
ffont Jan 24, 2024
b06c30c
Add get_parent similarity search mode
ffont Jan 24, 2024
12531aa
Add faceting and grouping support in similarity search
ffont Jan 26, 2024
c2c228b
Add support for similar_to param in search page
ffont Jan 26, 2024
b8a6e98
Support for limiting max search sounds dynamically
ffont Jan 26, 2024
92461a2
Use euclidean distance in similarity field type
ffont Jan 29, 2024
be81ad3
Update default similarity analyzers
ffont Jan 29, 2024
4da141d
Make test pass after new simialr_to parameter
ffont Jan 30, 2024
f9c1e29
Merge branch 'master' into similarity-solr
ffont Feb 1, 2024
f109ab7
Add option to display search results in map
ffont Feb 2, 2024
3abb353
Fix failing test
ffont Feb 2, 2024
44f7064
Make tests pass
ffont Feb 2, 2024
5f4b013
Add field_list parameter to search_sounds
ffont Feb 6, 2024
2c13e12
Optimize map query page load
ffont Feb 6, 2024
eafe53f
Add option to make query barray from request params
ffont Feb 7, 2024
30a1dc8
WIP Support for query-based map embeds
ffont Feb 7, 2024
ef508e1
Several improvements in solr-based maps
ffont Feb 8, 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
Prev Previous commit
Several improvements in solr-based maps
* Add support for solr-based queries in map
* Add navigation links from search page to map page and vice versa
 * Improvements in map embeds code
* Remove old unneeded geotags box code
* Use solr backend in tag/user/pack map pages
  • Loading branch information
ffont committed Feb 8, 2024
commit ef508e1392693876931bfaa953207a2f987455df
9 changes: 2 additions & 7 deletions accounts/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -287,14 +287,9 @@ def test_sound_search_response(self):
resp = self.client.get(reverse('sounds-search'))
self.assertEqual(resp.status_code, 200)

def test_geotags_box_response(self):
# 200 response on geotag box page access
resp = self.client.get(reverse('geotags-box'))
self.assertEqual(resp.status_code, 200)

def test_geotags_box_iframe_response(self):
def test_geotags_embed_response(self):
# 200 response on geotag box iframe
resp = self.client.get(reverse('embed-geotags-box-iframe'))
resp = self.client.get(reverse('embed-geotags'))
self.assertEqual(resp.status_code, 200)

def test_accounts_manage_pages(self):
2 changes: 1 addition & 1 deletion freesound/static/bw-frontend/src/components/mapsMapbox.js
Original file line number Diff line number Diff line change
@@ -292,7 +292,7 @@ function makeSoundsMap(geotags_url, map_element_id, on_built_callback, on_bounds
if (nSounds > 1){
// The padding and offset "manual" adjustments of bounds below are to make the boudns more similar to
// those created in the mapbox static maps
map.fitBounds(bounds, {duration:0, offset:[-10, 0], padding: {top:60, right:60, left:0, bottom:50}});
map.fitBounds(bounds, {duration:0, offset:[0, 0], padding: {top:60, right:60, left:60, bottom:60}});
} else {
map.setZoom(3);
if (nSounds > 0){
45 changes: 20 additions & 25 deletions freesound/static/bw-frontend/src/pages/map.js
Original file line number Diff line number Diff line change
@@ -12,10 +12,6 @@ const tagFilterInput = document.getElementById("tagFilter");
let currentLat;
let currentLon;
let currentZoom;
let currentBoxBlLa;
let currentBoxBlLon;
let currentBoxTrLat;
let currentBoxTrLon;

const toggleEmbedControls = () => {
if (embedControls.classList.contains('display-none')){
@@ -42,22 +38,21 @@ const updateQueryStringParameter = (uri, key, value) => {
}
}

const updateEmbedCode = (mapElementId, lat, lon, zoom, boxBlLat, boxBlLon, boxTrLat, boxTrLon) => {
const updateEmbedCode = (mapElementId, lat, lon, zoom) => {
if (embedCodeElement === null){ return; }

const mapCanvas = document.getElementById(mapElementId);
let mapCanvas;
if (mapElementId === undefined){
mapCanvas = document.getElementsByClassName('main-map')[0];
} else {
mapCanvas = document.getElementById(mapElementId);
}

// Store lat, lon and zoom globally so we can use them later to call updateEmbedCode without accessing map
currentLat = lat;
currentLon = lon;
currentZoom = zoom;
currentBoxBlLa = boxBlLat;
currentBoxBlLon = boxBlLon;
currentBoxTrLat = boxTrLat;
currentBoxTrLon = boxTrLon;

// Generate embed code
const box = "#box=" + boxBlLat + "," + boxBlLon+"," + boxTrLat+"," + boxTrLon;
const width = parseInt(embedWidthInputElement.value, 10);
const height = parseInt(embedHeightInputElement.value, 10);
let cluster = 'on';
@@ -66,16 +61,19 @@ const updateEmbedCode = (mapElementId, lat, lon, zoom, boxBlLat, boxBlLon, boxTr
}
let embedCode = "<iframe frameborder=\"0\" scrolling=\"no\" src=\"" + mapCanvas.dataset.geotagsEmbedBaseUrl
+ "?c_lat=" + lat + "&c_lon=" + lon + "&z=" + zoom + "&c=" + cluster + "&w=" + width + "&h=" + height;
if (mapCanvas.dataset.mapUsername !== "None"){
if (mapCanvas.dataset.mapUsername !== ""){
embedCode += "&username=" + mapCanvas.dataset.mapUsername;
}
if (mapCanvas.dataset.mapTag !== "None"){
if (mapCanvas.dataset.mapTag !== ""){
embedCode += "&tag=" + mapCanvas.dataset.mapTag;
}
if (mapCanvas.dataset.mapPackId !== "None"){
if (mapCanvas.dataset.mapPackId !== ""){
embedCode += "&pack=" + mapCanvas.dataset.mapPackId;
}
embedCode += box + "\" width=\"" + width + "\" height=\"" + height + "\"></iframe>";
if (mapCanvas.dataset.mapQp !== ""){
embedCode += "&qp=" + mapCanvas.dataset.mapQp;
}
embedCode += "\" width=\"" + width + "\" height=\"" + height + "\"></iframe>";
embedCodeElement.innerText = embedCode;

// Update page URL so it can directly be used to share the map
@@ -87,7 +85,7 @@ const updateEmbedCode = (mapElementId, lat, lon, zoom, boxBlLat, boxBlLon, boxTr
}

const changeEmbedWidthHeightCluster = () => {
updateEmbedCode(undefined, currentLat, currentLon, currentZoom, currentBoxBlLa, currentBoxBlLon, currentBoxTrLat, currentBoxTrLon);
updateEmbedCode(undefined, currentLat, currentLon, currentZoom);
}

const initMap = (mapCanvas) => {
@@ -107,7 +105,7 @@ const initMap = (mapCanvas) => {
[embedWidthInputElement, embedHeightInputElement, embedClusterCheckElement].forEach(element => {
if (element !== null){
element.addEventListener('change', () => {
changeEmbedWidthHeightCluster();
changeEmbedWidthHeightCluster();
});
}
});
@@ -140,13 +138,6 @@ const initMap = (mapCanvas) => {
zoom = mapCanvas.dataset.mapZoom;
}
let url = mapCanvas.dataset.geotagsUrl;
const urlBox = mapCanvas.dataset.geotagsUrlBox;
const box = document.location.hash.slice(5, document.location.hash.length);
if (box !== ''){
// If box is given, get the geotags only from that box
url = `${urlBox}?box=${box}`;
}

const showSearch = (mapCanvas.dataset.mapShowSearch !== undefined && mapCanvas.dataset.mapShowSearch === 'true');
const showStyleSelector = true;
const clusterGeotags = true;
@@ -156,7 +147,11 @@ const initMap = (mapCanvas) => {
if (loadingIndicator !== null){
loadingIndicator.innerText = `${numLoadedSounds} sound${ numLoadedSounds === 1 ? '': 's'}`;
}
embedWidthInputElement.value = mapCanvas.offsetWidth;
embedHeightInputElement.value = mapCanvas.offsetHeight;
}, updateEmbedCode, centerLat, centerLon, zoom, showSearch, showStyleSelector, clusterGeotags, showMapEvenIfNoGeotags);


}

export { initMap };
4 changes: 2 additions & 2 deletions freesound/urls.py
Original file line number Diff line number Diff line change
@@ -82,7 +82,7 @@
path('charts/', accounts.views.charts, name="charts"),

path('embed/sound/iframe/<int:sound_id>/simple/<player_size>/', sounds.views.embed_iframe, name="embed-simple-sound-iframe"),
path('embed/geotags_box/iframe/', geotags.views.embed_iframe, name="embed-geotags-box-iframe"),
path('embed/geotags_box/iframe/', geotags.views.embed_iframe, name="embed-geotags"),
path('oembed/', sounds.views.oembed, name="oembed-sound"),

path('after-download-modal/', sounds.views.after_download_modal, name="after-download-modal"),
@@ -93,7 +93,7 @@
path('browse/packs/', sounds.views.packs, name="packs"),
path('browse/random/', sounds.views.random, name="sounds-random"),
re_path(r'^browse/geotags/(?P<tag>[\w-]+)?/?$', geotags.views.geotags, name="geotags"),
path('browse/geotags_box/', geotags.views.geotags_box, name="geotags-box"),
path('browse/query/', geotags.views.for_query, name="geotags-query"),

path('contact/', support.views.contact, name="contact"),

14 changes: 7 additions & 7 deletions geotags/tests.py
Original file line number Diff line number Diff line change
@@ -40,13 +40,8 @@ def test_browse_geotags(self):
check_values = {'tag': 'soundscape', 'username': None}
self.check_context(resp.context, check_values)

def test_browse_geotags_box(self):
resp = self.client.get(reverse('geotags-box'))
check_values = {'center_lat': None, 'center_lon': None, 'zoom': None, 'username': None}
self.check_context(resp.context, check_values)

def test_geotags_box_iframe(self):
resp = self.client.get(reverse('embed-geotags-box-iframe'))
def test_geotags_embed(self):
resp = self.client.get(reverse('embed-geotags'))
check_values = {'m_width': 942, 'm_height': 600, 'cluster': True, 'center_lat': None, 'center_lon': None,
'zoom': None, 'username': None}
self.check_context(resp.context, check_values)
@@ -96,3 +91,8 @@ def test_browse_geotags_case_insensitive(self):
# Response contains 3 int32 objects per sound: id, lat and lng. Total size = 3 * 4 bytes = 12 bytes
n_sounds = len(resp.content) // 12
self.assertEqual(n_sounds, 2)

def test_browse_geotags_for_query(self):
resp = self.client.get(reverse('geotags-query') + f'?q=barcelona')
check_values = {'query_description': 'barcelona'}
self.check_context(resp.context, check_values)
1 change: 0 additions & 1 deletion geotags/urls.py
Original file line number Diff line number Diff line change
@@ -28,6 +28,5 @@
path('sounds_barray/sound/<int:sound_id>/', geotags.geotag_for_sound_barray, name="geotags-for-sound-barray"),
path('sounds_barray/query/', geotags.geotags_for_query_barray, name="geotags-for-query-barray"),
re_path(r'^sounds_barray/(?P<tag>[\w-]+)?/?$', geotags.geotags_barray, name="geotags-barray"),
path('geotags_box_barray/', geotags.geotags_box_barray, name="geotags-box-barray"),
path('infowindow/<int:sound_id>/', geotags.infowindow, name="geotags-infowindow"),
]
82 changes: 47 additions & 35 deletions geotags/views.py
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@
from django.urls import reverse
from django.views.decorators.cache import cache_page
from django.views.decorators.clickjacking import xframe_options_exempt
from accounts.models import Profile

from search.views import search_prepare_parameters
from sounds.models import Sound, Pack
@@ -51,7 +52,7 @@ def update_query_params_for_map_query(query_params, preserve_facets=False):
# Force is_geotagged filter to be present
if query_params['query_filter']:
if 'is_geotagged' not in query_params['query_filter']:
query_params['query_filter'] = query_params['query_filter'] + ' +is_geotagged:1'
query_params['query_filter'] = query_params['query_filter'] + ' is_geotagged:1'
else:
query_params['query_filter'] = 'is_geotagged:1'
# Force one single page with "all" results, and don't group by pack
@@ -113,37 +114,18 @@ def geotags_barray(request, tag=None):
return HttpResponse(generated_bytearray, content_type='application/octet-stream')


def geotags_box_barray(request):
box = request.GET.get("box", "-180,-90,180,90")
is_embed = request.GET.get("embed", "0") == "1"
try:
min_lat, min_lon, max_lat, max_lon = box.split(",")
qs = Sound.objects.select_related("geotag").exclude(geotag=None).filter(moderation_state="OK", processing_state="OK")
sounds = []
if min_lat <= max_lat and min_lon <= max_lon:
sounds = qs.filter(geotag__lat__range=(min_lat, max_lat)).filter(geotag__lon__range=(min_lon, max_lon))
elif min_lat > max_lat and min_lon <= max_lon:
sounds = qs.exclude(geotag__lat__range=(max_lat, min_lat)).filter(geotag__lon__range=(min_lon, max_lon))
elif min_lat <= max_lat and min_lon > max_lon:
sounds = qs.filter(geotag__lat__range=(min_lat, max_lat)).exclude(geotag__lon__range=(max_lon, min_lon))
elif min_lat > max_lat and min_lon > max_lon:
sounds = qs.exclude(geotag__lat__range=(max_lat, min_lat)).exclude(geotag__lon__range=(max_lon, min_lon))

generated_bytearray, num_geotags = generate_bytearray(sounds)
if num_geotags > 0:
log_map_load('box-embed' if is_embed else 'box', num_geotags, request)
return HttpResponse(generated_bytearray, content_type='application/octet-stream')
except ValueError:
raise Http404


@redirect_if_old_username_or_404
@raise_404_if_user_is_deleted
@cache_page(60 * 15)
def geotags_for_user_barray(request, username):
profile = get_object_or_404(Profile, user__username=username)
is_embed = request.GET.get("embed", "0") == "1"
sounds = Sound.public.select_related('geotag').filter(user__username__iexact=username).exclude(geotag=None)
generated_bytearray, num_geotags = generate_bytearray(sounds)
results, _ = perform_search_engine_query({
'query_filter': f'username:"{username}" is_geotagged:1', # No need to urlencode here as it will happpen somwhere before sending query to solr
'field_list': ['id', 'score', 'geotag'],
'num_sounds': profile.num_sounds,
})
generated_bytearray, num_geotags = generate_bytearray(results.docs)
if num_geotags > 0:
log_map_load('user-embed' if is_embed else 'user', num_geotags, request)
return HttpResponse(generated_bytearray, content_type='application/octet-stream')
@@ -160,8 +142,13 @@ def geotags_for_user_latest_barray(request, username):


def geotags_for_pack_barray(request, pack_id):
sounds = Sound.public.select_related('geotag').filter(pack__id=pack_id).exclude(geotag=None)
generated_bytearray, num_geotags = generate_bytearray(sounds)
pack = get_object_or_404(Pack, id=pack_id)
results, _ = perform_search_engine_query({
'query_filter': f'grouping_pack:"{pack.id}_{pack.name}" is_geotagged:1', # No need to urlencode here as it will happpen somwhere before sending query to solr
'field_list': ['id', 'score', 'geotag'],
'num_sounds': pack.num_sounds,
})
generated_bytearray, num_geotags = generate_bytearray(results.docs)
if num_geotags > 0:
log_map_load('pack', num_geotags, request)
return HttpResponse(generated_bytearray, content_type='application/octet-stream')
@@ -208,6 +195,7 @@ def _get_geotags_query_params(request):
def geotags(request, tag=None):
tvars = _get_geotags_query_params(request)
if tag is None:
query_search_page_url = ''
url = reverse('geotags-barray')
# If "all geotags map" and no lat/lon/zoom is indicated, center map so whole world is visible
if tvars['center_lat'] is None:
@@ -218,12 +206,14 @@ def geotags(request, tag=None):
tvars['zoom'] = 2
else:
url = reverse('geotags-barray', args=[tag])
query_search_page_url = reverse('sounds-search') + f'?f=tag:{tag}&mm=1'

tvars.update({ # Overwrite tag and username query params (if present)
'tag': tag,
'username': None,
'pack': None,
'url': url,
'query_search_page_url': query_search_page_url
})
return render(request, 'geotags/geotags.html', tvars)

@@ -238,6 +228,7 @@ def for_user(request, username):
'pack': None,
'sound': None,
'url': reverse('geotags-for-user-barray', args=[username]),
'query_search_page_url': reverse('sounds-search') + f'?f=username:{username}&mm=1'
})
return render(request, 'geotags/geotags.html', tvars)

@@ -278,6 +269,7 @@ def for_pack(request, username, pack_id):
'pack': pack,
'sound': None,
'url': reverse('geotags-for-pack-barray', args=[pack.id]),
'query_search_page_url': reverse('sounds-search') + f'?f=grouping_pack:"{pack.id}_{urllib.parse.quote(pack.name)}"&mm=1',
'modal_version': request.GET.get('ajax'),
})
if request.GET.get('ajax'):
@@ -288,12 +280,32 @@ def for_pack(request, username, pack_id):
return render(request, 'geotags/geotags.html', tvars)


def geotags_box(request):
# This view works the same as "geotags" but it takes the username/tag parameter from query parameters and
# onyl gets the geotags for a specific bounding box specified via hash parameters.
# Currently we are only keeping this as legacy because it is not used anymore but there might still be
# links pointing to it.
def for_query(request):
tvars = _get_geotags_query_params(request)
request_parameters_string = request.get_full_path().split('?')[-1]
q = request.GET.get('q', None)
f = request.GET.get('f', None)
query_description = ''
if q is None and f is None:
query_description = 'Empty query'
elif q is not None and f is not None:
query_description = f'{q} (some filters applied)'
else:
if q is not None:
query_description = q
if f is not None:
query_description = f'Empty query with some filtes applied'
tvars.update({
'tag': None,
'username': None,
'pack': None,
'sound': None,
'query_params': request_parameters_string,
'query_params_encoded': urllib.parse.quote(request_parameters_string),
'query_search_page_url': reverse('sounds-search') + f'?{request_parameters_string}',
'query_description': query_description,
'url': reverse('geotags-for-query-barray') + f'?{request_parameters_string}',
})
return render(request, 'geotags/geotags.html', tvars)


@@ -306,7 +318,7 @@ def embed_iframe(request):
'cluster': request.GET.get('c', 'on') != 'off'
})
tvars.update({'mapbox_access_token': settings.MAPBOX_ACCESS_TOKEN})
return render(request, 'embeds/geotags_box_iframe.html', tvars)
return render(request, 'embeds/geotags_embed.html', tvars)


def infowindow(request, sound_id):
Loading