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

H3 filter tool #833

Merged
merged 47 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c399fe2
Add h3-filter-ingest package
underbluewaters Sep 19, 2024
54d4a6e
Don't include cell id twice
underbluewaters Sep 19, 2024
b9ac5b0
fix db mode
underbluewaters Sep 19, 2024
d430cbe
WIP notes
underbluewaters Sep 19, 2024
39ec7c0
WIP
underbluewaters Sep 19, 2024
7503fdc
WIP
underbluewaters Sep 21, 2024
1a11515
Updates scripts
underbluewaters Sep 21, 2024
9f78fe9
WIP
underbluewaters Sep 21, 2024
5d560bf
WIP
underbluewaters Sep 23, 2024
3d32087
Merge branch 'master' into h3-filter-tool
underbluewaters Oct 2, 2024
5b72501
WIP
underbluewaters Oct 2, 2024
e8087e1
Merge branch 'master' into h3-filter-tool
underbluewaters Oct 9, 2024
29a471b
WIP
underbluewaters Oct 10, 2024
0ee4cee
WIP
underbluewaters Oct 14, 2024
adac17f
Filtering using postgres
underbluewaters Oct 17, 2024
2ae0c04
Added build scripts to test sqlite as an alternative to postgres, but…
underbluewaters Oct 18, 2024
7f5faeb
duckdb experiments
underbluewaters Oct 20, 2024
93e538e
WIP
underbluewaters Oct 20, 2024
8d5652e
duckdb api progress
underbluewaters Oct 21, 2024
1354988
Trying to add tile filitering
underbluewaters Oct 21, 2024
c03e888
Trying to add tile filitering
underbluewaters Oct 21, 2024
94f983d
WIP
underbluewaters Oct 25, 2024
41fde02
WIP
underbluewaters Oct 25, 2024
8a31efc
WIP
underbluewaters Oct 25, 2024
0572ba6
Working dynamic tile service!!
underbluewaters Oct 25, 2024
abd1668
WIP
underbluewaters Oct 26, 2024
ecf2f95
Add secret embed option for facilitating iframe usage of map
underbluewaters Oct 10, 2024
5d6729f
Fix exception when attempting to calculate ckmeans for more buckets t…
underbluewaters Oct 11, 2024
d12ea51
Fix for un-intentional report cache busting when copying a collection
underbluewaters Oct 29, 2024
0a74cd5
WIP form admin
underbluewaters Oct 30, 2024
3815cb7
WIP
underbluewaters Oct 31, 2024
aa1a558
Merge branch 'master' into h3-filter-tool
underbluewaters Oct 31, 2024
ef08e70
WIP
underbluewaters Nov 1, 2024
40af6f5
WIP
underbluewaters Nov 5, 2024
85fa00e
UI enhancements
underbluewaters Nov 6, 2024
a699d51
UI improvements
underbluewaters Nov 6, 2024
4bdb5f5
WIP
underbluewaters Nov 8, 2024
aeb8e09
Finished first beta of UI
underbluewaters Nov 8, 2024
4b2718f
Got rid of data processing scripts.
underbluewaters Nov 8, 2024
16a61b2
WIP
underbluewaters Nov 8, 2024
f08c8a1
WIP
underbluewaters Nov 8, 2024
56be1f1
Set maxzoom on viewable filter sketches
underbluewaters Nov 13, 2024
f038b08
Fix updates to count
underbluewaters Nov 13, 2024
030acfa
Fix typescript errors
underbluewaters Nov 13, 2024
98d3e04
Breaking migration into 2 parts
underbluewaters Nov 13, 2024
54c9c8f
Commited migrations
underbluewaters Nov 13, 2024
c4228a7
Improve "superusers only" option presentation
underbluewaters Nov 13, 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
6 changes: 6 additions & 0 deletions packages/api/generated-schema-clean.gql
Original file line number Diff line number Diff line change
Expand Up @@ -13060,6 +13060,7 @@ type Sketch implements Node {
"""
copyOf: Int
createdAt: Datetime!
filterMvtUrl: String

"""
Parent folder. Both regular sketches and collections may be nested within folders for organization purposes.
Expand Down Expand Up @@ -13162,6 +13163,8 @@ type SketchClass implements Node {
sketch classes can only be digitized by admins.
"""
canDigitize: Boolean
filterApiServerLocation: String
filterApiVersion: Int!

"""Reads a single `Form` that is related to this `SketchClass`."""
form: Form
Expand Down Expand Up @@ -13304,6 +13307,8 @@ input SketchClassPatch {

"""
allowMulti: Boolean
filterApiServerLocation: String
filterApiVersion: Int

"""
Geometry type users digitize. COLLECTION types act as a feature collection and have no drawn geometry.
Expand Down Expand Up @@ -13439,6 +13444,7 @@ enum SketchFoldersOrderBy {
enum SketchGeometryType {
CHOOSE_FEATURE
COLLECTION
FILTERED_PLANNING_UNITS
LINESTRING
POINT
POLYGON
Expand Down
6 changes: 6 additions & 0 deletions packages/api/generated-schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -13060,6 +13060,7 @@ type Sketch implements Node {
"""
copyOf: Int
createdAt: Datetime!
filterMvtUrl: String

"""
Parent folder. Both regular sketches and collections may be nested within folders for organization purposes.
Expand Down Expand Up @@ -13162,6 +13163,8 @@ type SketchClass implements Node {
sketch classes can only be digitized by admins.
"""
canDigitize: Boolean
filterApiServerLocation: String
filterApiVersion: Int!

"""Reads a single `Form` that is related to this `SketchClass`."""
form: Form
Expand Down Expand Up @@ -13304,6 +13307,8 @@ input SketchClassPatch {

"""
allowMulti: Boolean
filterApiServerLocation: String
filterApiVersion: Int

"""
Geometry type users digitize. COLLECTION types act as a feature collection and have no drawn geometry.
Expand Down Expand Up @@ -13439,6 +13444,7 @@ enum SketchFoldersOrderBy {
enum SketchGeometryType {
CHOOSE_FEATURE
COLLECTION
FILTERED_PLANNING_UNITS
LINESTRING
POINT
POLYGON
Expand Down
8 changes: 8 additions & 0 deletions packages/api/migrations/committed/000339.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--! Previous: sha1:1683d85c546802a54a712150f5991ca9f6b7655a
--! Hash: sha1:9bc19cbc712fb81e17c7a84ccee62a3facdf5ea5

-- Enter migration here
alter table sketch_classes add column if not exists filter_api_version int not null default 1;
alter table sketch_classes add column if not exists filter_api_server_location text;

alter type sketch_geometry_type add value if not exists 'FILTERED_PLANNING_UNITS';
275 changes: 275 additions & 0 deletions packages/api/migrations/committed/000340.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
--! Previous: sha1:9bc19cbc712fb81e17c7a84ccee62a3facdf5ea5
--! Hash: sha1:a98e9d208313bdb12a74b6a18f984d1d203092e1

-- Enter migration here
set role seasketch_superuser;
delete from sketch_classes where project_id = (select id from projects where slug = 'superuser') and name = 'Filtered Planning Units';

insert into sketch_classes(
project_id,
name,
geometry_type,
mapbox_gl_style,
is_template,
template_description
) values (
(select id from projects where slug = 'superuser'),
'Filtered Planning Units',
'FILTERED_PLANNING_UNITS'::sketch_geometry_type,
'{}'::jsonb,
true,
'Filter polygons by criteria. Requires an API server.'
) on conflict do nothing;

select initialize_sketch_class_form_from_template((select id from sketch_classes where name = 'Filtered Planning Units' and is_template = true), (select id from forms where is_template = true and template_type = 'SKETCHES' and template_name = 'Basic Template'));
set role postgres;

GRANT update (filter_api_server_location) on sketch_classes to seasketch_user;
GRANT update (filter_api_version) on sketch_classes to seasketch_user;

delete from form_element_types where component_name = 'FilterInput';
insert into form_element_types (
component_name,
label,
is_input,
is_surveys_only
) values (
'FilterInput',
'Filter Input',
true,
false
);

CREATE OR REPLACE FUNCTION public.before_sketch_insert_or_update() RETURNS trigger
LANGUAGE plpgsql SECURITY DEFINER
AS $$
declare
class_geometry_type sketch_geometry_type;
allow_multi boolean;
incoming_geometry_type text;
new_geometry_type text;
parent_collection_id int;
begin
select
geometry_type,
sketch_classes.allow_multi
into
class_geometry_type,
allow_multi,
incoming_geometry_type
from
sketch_classes
where
id = NEW.sketch_class_id;
if NEW.folder_id is not null and NEW.collection_id is not null then
raise exception 'Parent cannot be to both folder and collection';
end if;
if class_geometry_type = 'COLLECTION' then
-- Also check for parent collection of parent folder (recursively)
if NEW.collection_id is not null then
if (select get_parent_collection_id('sketch', NEW.collection_id)) is not null then
raise exception 'Nested collections are not allowed';
end if;
elsif NEW.folder_id is not null then
if (select get_parent_collection_id('sketch_folder', NEW.folder_id)) is not null then
raise exception 'Nested collections are not allowed';
end if;
end if;
-- geom must be present unless a collection
if NEW.geom is not null or NEW.user_geom is not null then
raise exception 'Collections should not have geometry';
else
-- no nested collections
if NEW.collection_id is not null then
raise exception 'Nested collections are not allowed';
else
return NEW;
end if;
end if;
elsif class_geometry_type = 'FILTERED_PLANNING_UNITS' then
-- Also check for parent collection of parent folder (recursively)
if NEW.collection_id is not null then
raise exception 'Filtered planning units cannot be part of a collection';
elsif NEW.folder_id is not null then
if (select get_parent_collection_id('sketch_folder', NEW.folder_id)) is not null then
raise exception 'Filtered planning units cannot be part of a collection';
end if;
end if;
-- geom must be present unless a collection
if NEW.geom is not null or NEW.user_geom is not null then
raise exception 'Filtered planning units should not have geometry';
else
-- no nested collections
if NEW.collection_id is not null then
raise exception 'Filtered planning units cannot be part of a collection';
else
return NEW;
end if;
end if;
else
select geometrytype(NEW.user_geom) into new_geometry_type;
-- geometry type must match sketch_class.geometry_type and sketch_class.allow_multi
if (new_geometry_type = class_geometry_type::text) or (allow_multi and new_geometry_type like '%' || class_geometry_type::text) then
-- if specifying a collection_id, must be in it's valid_children
if NEW.collection_id is null or not exists(select 1 from sketch_classes_valid_children where parent_id in (select sketch_class_id from sketches where id = NEW.collection_id)) or exists(select 1 from sketch_classes_valid_children where parent_id in (select sketch_class_id from sketches where id = NEW.collection_id) and child_id = NEW.sketch_class_id) then
return NEW;
else
raise exception 'Sketch is not a valid child of collection';
end if;
else
raise exception 'Geometry type does not match sketch class';
end if;
end if;
end;
$$;


CREATE OR REPLACE FUNCTION url_encode(input text)
RETURNS text AS $$
DECLARE
cleaned_input text;
output text := '';
ch text;
ch_code int;
BEGIN
-- Remove all extraneous whitespace from the input JSON text
cleaned_input := regexp_replace(input, '\s+', '', 'g');

-- Perform URL encoding on the cleaned input
FOR i IN 1..length(cleaned_input) LOOP
ch := substr(cleaned_input, i, 1);
ch_code := ascii(ch);
-- Allow only URL-safe characters (alphanumeric and unreserved characters)
IF ch ~ '[a-zA-Z0-9_.~-]' THEN
output := output || ch;
ELSE
-- Use lpad and upper to ensure two-character hexadecimal representation
output := output || '%' || lpad(upper(to_hex(ch_code)), 2, '0');
END IF;
END LOOP;

RETURN output;
END;
$$ LANGUAGE plpgsql;

grant execute on function url_encode to anon;
comment on function url_encode is '@omit';

drop function if exists filter_state_to_search_string(jsonb, int);
drop function if exists filter_state_to_search_string(jsonb);

CREATE OR REPLACE FUNCTION filter_state_to_search_string(filters jsonb, sketch_class_id int)
RETURNS text AS $$
DECLARE
state jsonb := '{}';
filter jsonb;
attr text;
result text;
attribute_name text;
filter_server_location text;
filter_version int;
final_url text;
BEGIN
-- Retrieve the filter_api_server_location and filter_api_version from sketch_classes table
SELECT sketch_classes.filter_api_server_location, sketch_classes.filter_api_version
INTO filter_server_location, filter_version
FROM sketch_classes
WHERE id = sketch_class_id;

-- If filter_api_server_location is NULL, return NULL
IF filter_server_location IS NULL THEN
RETURN NULL;
END IF;

-- Loop through each attribute in the input JSONB object
FOR attr, filter IN
SELECT key, value FROM jsonb_each(filters)
LOOP
-- Only process if "selected" is true
IF filter->>'selected' = 'true' THEN
if filter->>'attribute' is not null then
attribute_name := filter->>'attribute';
else
-- Look up the attribute name from form_elements for the current attr ID
SELECT component_settings->>'attribute'
INTO attribute_name
FROM form_elements
WHERE id = attr::int; -- Cast attr to integer to match form_elements ID
end if;


-- Default to the original key if no match is found
IF attribute_name IS NULL THEN
attribute_name := attr;
END IF;

-- Check for numberState
IF filter ? 'numberState' THEN
state := state || jsonb_build_object(attribute_name, jsonb_build_object(
'min', filter->'numberState'->'min',
'max', filter->'numberState'->'max'
));

-- Check for stringState
ELSIF filter ? 'stringState' THEN
state := state || jsonb_build_object(attribute_name, jsonb_build_object(
'choices', filter->'stringState'
));

-- Check for booleanState
ELSIF filter ? 'booleanState' THEN
state := state || jsonb_build_object(attribute_name, jsonb_build_object(
'bool', COALESCE(filter->'booleanState', 'false')::boolean
));
END IF;
END IF;
END LOOP;

-- If no keys were added, return an empty string; otherwise, encode the JSON
IF state = '{}'::jsonb THEN
RETURN filter_server_location || '/v' || filter_version || '/mvt/{z}/{x}/{y}.pbf';
ELSE
-- Convert the JSONB object to a text string without formatting
result := state::text;

-- URL encode the resulting JSON text using the updated url_encode function
result := url_encode(result);

-- Construct the final URL
final_url := filter_server_location || '/v' || filter_version || '/mvt/{z}/{x}/{y}.pbf?filter=' || result;
RETURN final_url;
END IF;
END;
$$ LANGUAGE plpgsql;

grant execute on function filter_state_to_search_string to anon;
comment on function filter_state_to_search_string is '@omit';

alter table sketches drop column if exists filter_mvt_url;
create or replace function sketches_filter_mvt_url(s sketches)
returns text
language sql
security definer
stable
as $$
select filter_state_to_search_string(s.properties, s.sketch_class_id);
$$;


grant execute on function sketches_filter_mvt_url to anon;
-- alter table sketches add column if not exists filter_mvt_url text generated always as (filter_state_to_search_string(properties, sketch_class_id)) stored;

-- grant select(filter_mvt_url) on sketches to anon;

delete from form_element_types where component_name = 'CollapsibleGroup';
insert into form_element_types (
component_name,
label,
is_input,
is_surveys_only
) values (
'CollapsibleGroup',
'Collapsible Group',
false,
false
);
2 changes: 1 addition & 1 deletion packages/api/migrations/current.sql
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-- Enter migration here
-- Enter migration here
Loading
Loading