diff --git a/mappyfile.pyproj b/mappyfile.pyproj index 7316be0..687078d 100644 --- a/mappyfile.pyproj +++ b/mappyfile.pyproj @@ -12,7 +12,7 @@ mappyfile mappyfile MSBuild|mappyfile|$(MSBuildProjectFullPath) - tests\test_utils.py + tests\test_snippets.py Standard Python launcher False PATH=C:\MapServer\bin;%PATH% @@ -208,6 +208,7 @@ PROJ_LIB=C:\MapServer\bin\proj\SHARE + diff --git a/mappyfile/mapfile.lark b/mappyfile/mapfile.lark index d416530..5fea106 100644 --- a/mappyfile/mapfile.lark +++ b/mappyfile/mapfile.lark @@ -42,7 +42,7 @@ composite: composite_type composite_body _END | connectionoptions composite_body: _composite_item* -_composite_item: (composite|attr|points|projection|pattern|values|config) +_composite_item: (composite|attr|points|projection|pattern|values|config|classauto) !projection: "PROJECTION"i (string*|AUTO) _END !config: "CONFIG"i (string | UNQUOTED_STRING) (string | UNQUOTED_STRING) @@ -78,6 +78,7 @@ string_pair: (string|UNQUOTED_STRING) (string|UNQUOTED_STRING) attr_bind_pair: attr_bind attr_bind attr_mixed_pair: attr_bind (int|float) | (int|float) attr_bind +classauto: CLASSAUTO float: SIGNED_FLOAT float_pair: float float path: PATH @@ -124,6 +125,7 @@ func_params: atom ("," atom)* | "COMPOSITE"i | "FEATURE"i | "GRID"i + | "IDENTIFY"i | "JOIN"i | "LABEL"i | "LAYER"i @@ -139,6 +141,7 @@ func_params: atom ("," atom)* | "WEB"i | "SYMBOL"i +CLASSAUTO: "CLASSAUTO"i AUTO: "AUTO"i PATH: /([a-z0-9_]*\.*\/|[a-z0-9_]+[.\/])[a-z0-9_\/\.-]+/i diff --git a/mappyfile/pprint.py b/mappyfile/pprint.py index 4ea8f10..758adfd 100644 --- a/mappyfile/pprint.py +++ b/mappyfile/pprint.py @@ -151,7 +151,11 @@ def __format_line( ) -> str: if (aligned_max_indent is None) or (aligned_max_indent == 0): aligned_max_indent = len(key) + 1 - indent = " " * (aligned_max_indent - len(key)) + if value == "": + indent = "" # for keywords with no values, do not add a space + else: + indent = " " * (aligned_max_indent - len(key)) + tmpl = "{spacer}{key}{indent}{value}" d = {"spacer": spacer, "key": key, "value": value, "indent": indent} return tmpl.format(**d) @@ -399,6 +403,12 @@ def format_value(self, attr: str, attr_props, value: Any) -> Any: return value + if "type" in attr_props and attr_props["type"] == "null": + # for keywords without a value return an empty string + # currently only CLASSAUTO is the only keyword without a value + assert attr.lower() == "classauto" + return "" + if ( "type" in attr_props and attr_props["type"] == "string" ): # and "enum" not in attr_props diff --git a/mappyfile/schemas/identify.json b/mappyfile/schemas/identify.json new file mode 100644 index 0000000..5b71e49 --- /dev/null +++ b/mappyfile/schemas/identify.json @@ -0,0 +1,23 @@ +{ + "type": "object", + "additionalProperties": false, + "metadata": { + "minVersion": 8.6 + }, + "patternProperties": { + "^__[a-z]+__$": true + }, + "properties": { + "__type__": { + "const": "identify" + }, + "tolerance": { + "type": "number" + }, + "toleranceunits": { + "enum": [ "feet", "inches", "kilometers", "meters", "miles", "nauticalmiles", "pixels", "dd" ] + }, + "classauto": { "type": "null" } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema" +} \ No newline at end of file diff --git a/mappyfile/schemas/layer.json b/mappyfile/schemas/layer.json index 091b8f8..d31ec82 100644 --- a/mappyfile/schemas/layer.json +++ b/mappyfile/schemas/layer.json @@ -159,6 +159,12 @@ "header": { "type": "string" }, + "identify": { + "$ref": "identify.json", + "metadata": { + "minVersion": 8.6 + } + }, "joins": { "type": "array", "items": { diff --git a/mappyfile/tokens.py b/mappyfile/tokens.py index 69c73b2..a4a4a27 100644 --- a/mappyfile/tokens.py +++ b/mappyfile/tokens.py @@ -43,6 +43,7 @@ composite feature grid + identify join label leader @@ -111,6 +112,7 @@ graticule group header + identify image imagecolor imagetype @@ -256,6 +258,7 @@ cluster connectionoptions grid + identify leader legend metadata diff --git a/mappyfile/transformer.py b/mappyfile/transformer.py index f3b7f97..a56fab7 100644 --- a/mappyfile/transformer.py +++ b/mappyfile/transformer.py @@ -642,6 +642,15 @@ def list(self, t): v.value = "{%s}" % list_values return v + def classauto(self, t): + key_token = t[0] + key_name = self.key_name(key_token) + pd = self.create_position_dict(key_token, None) + d: dict = OrderedDict() + d["__position__"] = pd + d[key_name] = None + return d + class CommentsTransformer(Transformer_InPlace): """ diff --git a/tests/sample_maps/point_symbol_identification.map b/tests/sample_maps/point_symbol_identification.map new file mode 100644 index 0000000..51f811a --- /dev/null +++ b/tests/sample_maps/point_symbol_identification.map @@ -0,0 +1,294 @@ +# +# Test GetFeatureInfo on points with symbols +# +# REQUIRES: SUPPORTS=WMS +# + +# RUN_PARMS: point_symbol_identification_rotation_30.png [MAPSERV] QUERY_STRING="map=[MAPFILE]&REQUEST=GetMap&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=" > [RESULT_DEMIME] +# RUN_PARMS: point_symbol_identification_rotation_30_i_73_j_82.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_rotation_30&INFO_FORMAT=application%2Fvnd.ogc.gml&I=73&J=82" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_rotation_30_i_73_j_82_with_styles.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=test&QUERY_LAYERS=test_rotation_30&INFO_FORMAT=application%2Fvnd.ogc.gml&I=73&J=82" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_rotation_30_i_70_j_82.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_rotation_30&INFO_FORMAT=application%2Fvnd.ogc.gml&I=70&J=82" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_rotation_30_i_118_j_78.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_rotation_30&INFO_FORMAT=application%2Fvnd.ogc.gml&I=111&J=78" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_rotation_30_i_159_j_91.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_rotation_30&INFO_FORMAT=application%2Fvnd.ogc.gml&I=159&J=91" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_rotation_30_classgroup_i_159_j_91.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_classgroup&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_classgroup&INFO_FORMAT=application%2Fvnd.ogc.gml&I=159&J=91&FEATURE_COUNT=10" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_wrong_layer.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=wrong_layer&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_classgroup&INFO_FORMAT=application%2Fvnd.ogc.gml&I=159&J=91" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_wrong_styles.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=wrong_styles&QUERY_LAYERS=test_rotation_30&INFO_FORMAT=application%2Fvnd.ogc.gml&I=73&J=82" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_wrong_number_of_styles.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=test,test&QUERY_LAYERS=test_rotation_30&INFO_FORMAT=application%2Fvnd.ogc.gml&I=73&J=82" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_two_layers.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_rotation_30,test_classgroup&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=test,test&QUERY_LAYERS=test_rotation_30,test_classgroup&INFO_FORMAT=application%2Fvnd.ogc.gml&I=73&J=82&NUM_FEATURES=10" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_class_minscaledenom_5700.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_class_minscaledenom_5700&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_class_minscaledenom_5700&INFO_FORMAT=application%2Fvnd.ogc.gml&I=73&J=82" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_style_minscaledenom_5700.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_style_minscaledenom_5700&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_style_minscaledenom_5700&INFO_FORMAT=application%2Fvnd.ogc.gml&I=73&J=82" > [RESULT_DEVERSION] +# RUN_PARMS: point_symbol_identification_expression_false.xml [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&REQUEST=GetFeatureInfo&SERVICE=WMS&VERSION=1.1.0&LAYERS=test_expression_false&SRS=EPSG:3857&BBOX=1000,2000,1400,2300&FORMAT=png&WIDTH=200&HEIGHT=150&STYLES=&QUERY_LAYERS=test_expression_false&INFO_FORMAT=application%2Fvnd.ogc.gml&I=73&J=82" > [RESULT_DEVERSION] + +# RUN_PARMS: point_symbol_identification_mode_query_classauto.txt [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&mode=query&layer=test_classgroup&qlayer=test_rotation_30&imgxy=73+62&imgext=1000+2000+1400+2300&imgsize=200+150" > [RESULT_DEMIME] +# RUN_PARMS: point_symbol_identification_mode_query_classgroup.txt [MAPSERV] QUERY_STRING="MAP=[MAPFILE]&mode=query&layer=test_classgroup&qlayer=test_classgroup&imgxy=73+62&imgext=1000+2000+1400+2300&imgsize=200+150" > [RESULT_DEMIME] + +MAP + +STATUS ON +EXTENT 1000 2000 1400 2300 +SIZE 200 150 +IMAGECOLOR 127 127 127 +SYMBOLSET "etc/symbols.sym" + +WEB + METADATA + "ows_updatesequence" "123" + "wms_title" "Test simple wms" + "wms_onlineresource" "http://localhost/path/to/wms_simple?" + "wms_srs" "EPSG:3857" + "ows_schemas_location" "http://schemas.opengis.net" + "ows_keywordlist" "ogc,wms,mapserver" + "ows_service_onlineresource" "http://www.mapserver.org/" + "ows_fees" "None" + "ows_accessconstraints" "None" + "ows_addresstype" "postal" + "ows_address" "123 SomeRoad Road" + "ows_city" "Toronto" + "ows_stateorprovince" "Ontario" + "ows_postcode" "xxx-xxx" + "ows_country" "Canada" + "ows_contactelectronicmailaddress" "tomkralidis@xxxxxxx.xxx" + "ows_contactvoicetelephone" "+xx-xxx-xxx-xxxx" + "ows_contactfacsimiletelephone" "+xx-xxx-xxx-xxxx" + "ows_contactperson" "Tom Kralidis" + "ows_contactorganization" "MapServer" + "ows_contactposition" "self" + + "ows_rootlayer_title" "My Layers" + "ows_rootlayer_abstract" "These are my layers" + "ows_rootlayer_keywordlist" "layers,list" + "ows_enable_request" "*" # not read at all, all layers contains their setting. + END +END + + PROJECTION + "init=epsg:3857" + END + + WEB + QUERYFORMAT 'tmpl' + END + + OUTPUTFORMAT + NAME 'tmpl' + DRIVER 'TEMPLATE' + MIMETYPE 'text/plain' + FORMATOPTION "FILE=templates/point_symbol_identification.tmpl" + END + +LAYER + NAME "test_rotation_30" + TYPE point + STATUS OFF + TEMPLATE "ttt" + + METADATA + "wms_title" "test_rotation_30" + "wms_description" "test_rotation_30" + "wms_srs" "EPSG:3857" + "gml_include_items" "all" + "ows_enable_request" "*" + END + + EXTENT 1000 2000 1400 2300 + + IDENTIFY + CLASSAUTO + END + + PROJECTION + "init=epsg:3857" + END + + CLASS + GROUP "test" + STYLE + ANGLE 30 + SYMBOL "Road_Works_inner_transparent" + END + END + + FEATURE + POINTS + 1200 2150 + END + END +END + + +LAYER + NAME "test_classgroup" + TYPE point + STATUS OFF + TEMPLATE "ttt" + + EXTENT 1000 2000 1400 2300 + + METADATA + "wms_title" "test_classgroup" + "wms_description" "test_classgroup" + "wms_srs" "EPSG:3857" + "gml_include_items" "all" + "ows_enable_request" "*" + END + + IDENTIFY + CLASSGROUP "test" + END + + PROJECTION + "init=epsg:3857" + END + + CLASS + GROUP "test" + STYLE + ANGLE 30 + SYMBOL "Road_Works_inner_transparent" + END + END + + FEATURE + POINTS + 1200 2100 + END + END + + FEATURE + POINTS + 1200 2150 + END + END + + FEATURE + POINTS + 1201 2151 + END + END +END + + +LAYER + NAME "test_class_minscaledenom_5700" + TYPE point + STATUS OFF + TEMPLATE "ttt" + + METADATA + "wms_title" "test_class_minscaledenom_5700" + "wms_description" "test_class_minscaledenom_5700" + "wms_srs" "EPSG:3857" + "gml_include_items" "all" + "ows_enable_request" "*" + END + + EXTENT 1000 2000 1400 2300 + + IDENTIFY + CLASSAUTO + END + + PROJECTION + "init=epsg:3857" + END + + CLASS + MINSCALEDENOM 5700 + GROUP "test" + STYLE + ANGLE 30 + SYMBOL "Road_Works_inner_transparent" + END + END + + FEATURE + POINTS + 1200 2150 + END + END +END + + +LAYER + NAME "test_style_minscaledenom_5700" + TYPE point + STATUS OFF + TEMPLATE "ttt" + + METADATA + "wms_title" "test_style_minscaledenom_5700" + "wms_description" "test_style_minscaledenom_5700" + "wms_srs" "EPSG:3857" + "gml_include_items" "all" + "ows_enable_request" "*" + END + + EXTENT 1000 2000 1400 2300 + + IDENTIFY + CLASSAUTO + END + + PROJECTION + "init=epsg:3857" + END + + CLASS + GROUP "test" + STYLE + MINSCALEDENOM 5700 + ANGLE 30 + SYMBOL "Road_Works_inner_transparent" + END + END + + FEATURE + POINTS + 1200 2150 + END + END +END + + +LAYER + NAME "test_expression_false" + TYPE point + STATUS OFF + TEMPLATE "ttt" + + METADATA + "wms_title" "test_expression_false" + "wms_description" "test_expression_false" + "wms_srs" "EPSG:3857" + "gml_include_items" "all" + "ows_enable_request" "*" + END + + EXTENT 1000 2000 1400 2300 + + IDENTIFY + CLASSAUTO + END + + PROJECTION + "init=epsg:3857" + END + + CLASS + EXPRESSION (false) + GROUP "test" + STYLE + ANGLE 30 + SYMBOL "Road_Works_inner_transparent" + END + END + + FEATURE + POINTS + 1200 2150 + END + END +END + + +END diff --git a/tests/sample_maps/wms_getfeatureinfo_datatypes_raster.map b/tests/sample_maps/wms_getfeatureinfo_datatypes_raster.map new file mode 100644 index 0000000..1cf4d7b --- /dev/null +++ b/tests/sample_maps/wms_getfeatureinfo_datatypes_raster.map @@ -0,0 +1,70 @@ +# +# Test WMS GetfeatureInfo with a raster Int32 layer +# +# REQUIRES: SUPPORTS=WMS +# +# RUN_PARMS: wms_getfeatureinfo_int32_raster.json [MAPSERV] "QUERY_STRING=map=[MAPFILE]&service=WMS&request=GetFeatureInfo&version=1.3.0&CRS=EPSG:2157&width=200&height=200&layers=int32&bbox=600181.89,628797.70,600191.27,628807.09&query_layers=int32&i=50&j=50&STYLES=&info_format=geojson" > [RESULT_DEMIME] +# RUN_PARMS: wms_getfeatureinfo_float64_raster.json [MAPSERV] "QUERY_STRING=map=[MAPFILE]&service=WMS&request=GetFeatureInfo&version=1.3.0&CRS=EPSG:2157&width=200&height=200&layers=float64&bbox=600181.89,628797.70,600191.27,628807.09&query_layers=float64&i=50&j=50&STYLES=&info_format=geojson" > [RESULT_DEMIME] + +MAP + + NAME 'test' + EXTENT 600029.81 628680.39 600319.81 628905.39 + SIZE 600 600 + IMAGETYPE PNG + PROJECTION + "init=epsg:2157" + END + + WEB + METADATA + "ows_enable_request" "*" + "ows_srs" "EPSG:2157" + "ows_title" "WMS Getfeatureinfo" + "wms_getfeatureinfo_formatlist" "geojson" + "wms_onlineresource" "http://localhost/cgi-bin/mapserv?map=mymap.map" + END + END + + OUTPUTFORMAT + NAME "geojson" + DRIVER "OGR/GEOJSON" + MIMETYPE "application/json; subtype=geojson; charset=utf-8" + FORMATOPTION "FORM=SIMPLE" + FORMATOPTION "STORAGE=memory" + FORMATOPTION "LCO:NATIVE_MEDIA_TYPE=application/vnd.geo+json" + FORMATOPTION "USE_FEATUREID=true" + END + + + LAYER + NAME "int32" + STATUS OFF + TYPE RASTER + TEMPLATE "blank" + DATA "data/int32.tif" + PROJECTION + "EPSG:2157" + END + METADATA + "gml_include_items" "all" + "gml_value_0_type" "Long" + END + END + + LAYER + NAME "float64" + STATUS OFF + TYPE RASTER + TEMPLATE "blank" + DATA "data/float64.tif" + PROJECTION + "EPSG:2157" + END + METADATA + "gml_include_items" "all" + "gml_value_0_type" "Real" + END + END + +END # of map file diff --git a/tests/test_snippets.py b/tests/test_snippets.py index 2dcaa22..856ce2e 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -1036,17 +1036,7 @@ def test_class_regex_expression(): assert output(s, schema_name="class") == exp -def run_tests(): - r""" - Need to comment out the following line in C:\VirtualEnvs\mappyfile\Lib\site-packages\pep8.py - #stdin_get_value = sys.stdin.read - Or get AttributeError: '_ReplInput' object has no attribute 'read' - """ - # pytest.main(["tests/test_snippets.py::test_style_pattern"]) - pytest.main(["tests/test_snippets.py"]) - - -def test_class_fallack(): +def test_class_fallback(): s = """ CLASS STYLE @@ -1060,10 +1050,36 @@ def test_class_fallack(): assert output(s, schema_name="class") == exp +def test_layer_identify(): + s = """ +LAYER + TYPE POLYGON + IDENTIFY + TOLERANCE 5 # optional + TOLERANCEUNITS meters + CLASSAUTO + END +END""" + + print(output(s, schema_name="layer")) + exp = "LAYER TYPE POLYGON IDENTIFY TOLERANCE 5 TOLERANCEUNITS METERS CLASSAUTO END END" + assert output(s, schema_name="layer") == exp + + +def run_tests(): + r""" + Need to comment out the following line in C:\VirtualEnvs\mappyfile\Lib\site-packages\pep8.py + #stdin_get_value = sys.stdin.read + Or get AttributeError: '_ReplInput' object has no attribute 'read' + """ + # pytest.main(["tests/test_snippets.py::test_style_pattern"]) + pytest.main(["tests/test_snippets.py"]) + + if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) # test_multiple_compfilters() # test_geomtransform_nested_function() - test_class_fallack() + test_layer_identify() # run_tests() print("Done!")