├── .gitignore ├── LICENSE.txt ├── README.md ├── example ├── example.py └── example_template.yml ├── pyproject.toml └── src └── military_symbol ├── OFL.txt ├── Roboto.ttf ├── __init__.py ├── command_line.py ├── constants_parser.py ├── drawing_items.py ├── font_rendering.py ├── name_combiner.py ├── name_to_sidc.py ├── output_style.py ├── schema.py ├── schema ├── activities.json ├── air.json ├── air_missile.json ├── common_modifiers.json ├── constants.json ├── cyberspace.json ├── dismounted_individual.json ├── land_civilian.json ├── land_equipment.json ├── land_installations.json ├── land_unit.json ├── mine_warfare.json ├── sea_subsurface.json ├── sea_surface.json ├── signals_intelligence.json ├── space.json └── space_missile.json ├── symbol.py ├── symbol_cache.py └── template.py /.gitignore: -------------------------------------------------------------------------------- 1 | /Resources/symbols/ 2 | /venv/ 3 | /example/*.svg 4 | /.stfolder/ 5 | /.idea 6 | /*.svg 7 | /dist/ 8 | /src/military_symbol.egg-info/ 9 | /__pycache__/ 10 | /Examples/ 11 | /src/military_symbol/__pycache__/ 12 | /test/ 13 | /src/__pycache__/ 14 | /src/test/ 15 | *.svg -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Nicholas Royer. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python military symbols 2 | 3 | This is a lightweight Python module, including a command-line script, to generate NATO APP-6(D) compliant military symbol icons in SVG format. These SVGs can be generated from inputs formatted as NATO SIDCs (Symbol identification codes) or as natural-language names for symbols, i.e. "friendly infantry platoon" or "enemy mortar section." Features supported include: 4 | 5 | - Headquarters, task force, mobility, and echelon indicators 6 | - Automatic checking of modifier and entity types for congruence 7 | - Symbol sets to indicate land units and installations, air, space, sea surface, subsurface, signals intelligence, and cyber units, tracks, and activities 8 | - Status indicators in both standard and alternate forms 9 | - Construction of SVGs in light, medium, dark, and unfilled styles 10 | 11 | Control measure graphics are not yet implemented. 12 | 13 | Available as a [Python package](https://pypi.org/project/military-symbol/1.0.0/). 14 | 15 | ### Usage 16 | 17 | Command line usage examples: 18 | 19 | ```bash 20 | # Create a set of symbols by name, using variant symbols if available, in the current directory 21 | military_symbol --use-variants --by-name -o . "Friendly artillery company" "Destroyed Enemy PSYOP section" 22 | 23 | # Create a single symbol at the designated path by name 24 | military_symbol -o platoon.svg -n "Friendly infantry platoon" 25 | 26 | # Print a set of symbols by name, in unfilled style, to STDOUT 27 | military_symbol -s unfilled -n "Friendly infantry platoon" "Enemy anti-air battery" 28 | 29 | # Create the same set of symbols as above, but by SIDC 30 | military_symbol -o . -n 10031000141211000000 10041000141211000000 31 | ``` 32 | 33 | Python module usage: 34 | 35 | ```Python 36 | import os 37 | 38 | import military_symbol 39 | 40 | if __name__ == '__main__': 41 | # Print symbol generated from a name to STDOUT 42 | print(military_symbol.get_symbol_svg_string_from_name("enemy infantry platoon")) 43 | 44 | # Add a symbol template and write it to a file adjacent to this script 45 | example_template_directory = os.path.dirname(__file__) 46 | military_symbol.add_symbol_template_set(os.path.join(example_template_directory, 'example_template.json')) 47 | military_symbol.write_symbol_svg_string_from_name("T-82", out_filepath=os.path.join(example_template_directory, 48 | 'T-82.svg'), auto_name=False) 49 | 50 | shapes = [ 51 | 'friendly infantry', 52 | 'friendly cavalry', 53 | 'friendly artillery' 54 | ] 55 | for shape in shapes: 56 | military_symbol.write_symbol_svg_string_from_name(shape, out_filepath=example_template_directory) 57 | 58 | # Generate a list of symbols from names and write them as SVG files in specific 59 | # styles, named according to a user-defined pattern and using variant symbols where available 60 | examples = [ 61 | ('Enemy armor company', 'light'), 62 | ("Dummy damaged neutral hospital", 'medium'), 63 | ("Friendly fighter", 'dark'), 64 | ("Destroyed neutral artillery task force headquarters", 'unfilled'), 65 | ("Suspected CBRN section", 'light') 66 | ] 67 | 68 | for example_name, example_style in examples: 69 | example_symbol: military_symbol.individual_symbol.MilitarySymbol = military_symbol.get_symbol_class_from_name(example_name) 70 | print('Exporting symbol "{}"'.format(example_symbol.get_name())) 71 | 72 | output_filename = '{} ({}).svg'.format(example_symbol.get_sidc(), example_style) 73 | with open(output_filename, 'w') as output_file: 74 | output_file.write(example_symbol.get_svg(style=example_style, pixel_padding=4, use_variants=True)) 75 | ``` 76 | 77 | ## License 78 | 79 | This project is licensed under the MIT license. 80 | -------------------------------------------------------------------------------- /example/example.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimal example class demonstrating usage as a Python module, including use of templates, writing to files, and to the 3 | command line 4 | """ 5 | 6 | import os 7 | import inspect 8 | 9 | import military_symbol 10 | 11 | if __name__ == '__main__': 12 | # Print symbol genera/home/nick/Projects/Python-Military-Symbols/Resources/_symbol_converter.pyted from a name to STDOUT 13 | # print(military_symbol.get_symbol_svg_string_from_name("enemy infantry platoon")) 14 | 15 | # Add a symbol template and write it to a file adjacent to this script 16 | military_symbol.add_templates_from_file('example_template.yml') 17 | 18 | test_dir = os.path.join(os.path.dirname(__file__), '..', 'test') 19 | os.makedirs(test_dir, exist_ok=True) 20 | 21 | # Generate a list of symbols from names and write them as SVG files in specific 22 | # styles, named according to a user-defined pattern and using variant symbols where available 23 | examples = [ 24 | ('hostile armor company', 'light'), 25 | ("Dummy damaged neutral hospital", 'medium'), 26 | ("Friendly fighter", 'dark'), 27 | ("Destroyed neutral artillery task force headquarters", 'unfilled'), 28 | ("Suspected CBRN section", 'light'), 29 | ('hostile TOS-1 battery', 'unfilled'), 30 | ('friendly amphibious HIMARS battery', 'light'), 31 | ('friendly amphibious HIMARS battery', 'medium') 32 | ] 33 | 34 | for example_name, example_style in examples: 35 | example_symbol, svg_string = military_symbol.get_symbol_and_svg_string(example_name, 36 | is_sidc=False, 37 | style=example_style, 38 | padding=4, 39 | use_variants=True, 40 | use_background=True) 41 | 42 | # print('\tExporting symbol "{}"'.format(example_symbol.get_name())) 43 | 44 | output_filename = os.path.join(os.getcwd(), '{} {} ({}).svg'.format(example_name, example_symbol.get_sidc(), example_style)) 45 | with open(output_filename, 'w') as output_file: 46 | output_file.write(svg_string) 47 | -------------------------------------------------------------------------------- /example/example_template.yml: -------------------------------------------------------------------------------- 1 | HIMARS: 2 | sidc: 13 0* 10 0 0 ** 130300 41 06 01 00000*** 3 | 4 | TOS-1: 5 | symbol_set: 10 6 | entity: 130300 7 | modifier_1: 59 8 | modifier_2: 104 -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "military-symbol" 7 | version = "2.0.2" 8 | authors = [ 9 | { name="Nicholas Royer", email="nick.w.royer@protonmail.com" }, 10 | ] 11 | description = "A lightweight library for producing SVGs of NATO standard military symbols from NATO sidcs or natural-language descriptions" 12 | readme = "README.md" 13 | requires-python = ">=3.8" 14 | classifiers = [ 15 | "Development Status :: 4 - Beta", 16 | "Programming Language :: Python :: 3", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | "Natural Language :: English", 20 | 21 | "Topic :: Scientific/Engineering :: GIS", 22 | "Topic :: Multimedia :: Graphics" 23 | ] 24 | license = "MIT" 25 | license-files = ["LICEN[CS]E*"] 26 | dependencies = [ 27 | "lxml", 28 | "svgpathtools", 29 | "thefuzz", 30 | "freetype-py" 31 | ] 32 | 33 | [project.scripts] 34 | military-symbol = "military_symbol.command_line:command_line_main" 35 | 36 | [project.urls] 37 | Homepage = "https://github.com/nwroyer/Python-Military-Symbols" 38 | Issues = "https://github.com/nwroyer/Python-Military-Symbols/issues" 39 | 40 | [tool.hatch.build.targets.sdist] 41 | exclude = [ 42 | "test", 43 | "src/test", 44 | "src/military_symbol/test", 45 | "*.svg" 46 | ] -------------------------------------------------------------------------------- /src/military_symbol/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | https://openfontlicense.org 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /src/military_symbol/Roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwroyer/Python-Military-Symbols/0fd7398c9622a050f9a0b9a631cf23de469e9b77/src/military_symbol/Roboto.ttf -------------------------------------------------------------------------------- /src/military_symbol/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.append(os.path.dirname(__file__)) 5 | from command_line import * 6 | from schema import Schema as Schema 7 | from symbol import Symbol as Symbol 8 | from template import Template as Template 9 | from output_style import OutputStyle as OutputStyle 10 | 11 | if __name__ == '__main__': 12 | command_line_main() 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/military_symbol/command_line.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import os.path 4 | import sys 5 | 6 | sys.path.append(os.path.dirname(__file__)) 7 | import name_to_sidc 8 | 9 | from symbol import Symbol 10 | from schema import Schema 11 | from template import Template 12 | # from symbol_template import SymbolTemplateSet 13 | from symbol_cache import SymbolCache 14 | 15 | VERSION = "2.0.2" 16 | STYLE_CHOICES = ['light', 'medium', 'dark', 'unfilled'] 17 | 18 | # Load the symbol schema from its default location; don't 19 | sym_schema: Schema = Schema.load_from_directory() 20 | if sym_schema is None: 21 | print("Error loading symbol schema; exiting", file=sys.stderr) 22 | 23 | symbol_cache: SymbolCache = SymbolCache(sym_schema) 24 | 25 | def add_templates_from_file(template_filename:str): 26 | """ 27 | Add a symbol template to allow easier generation of symbols by name for specific situation. 28 | :param template_filename: The filename for the template file is structured as shown in the example_template.json file 29 | """ 30 | try: 31 | templates = Template.load_from_file(template_filename) 32 | sym_schema.add_templates(templates) 33 | except Exception as ex: 34 | print(f'Error adding templates from "{template_filename}: {ex}') 35 | return 36 | 37 | 38 | def get_symbol_svg_string_from_sidc(sidc, bounding_padding=4, verbose=False, use_variants=False, style='light', 39 | use_background=False, background_color='#ffffff', force_all_elements=False) -> str: 40 | """ 41 | Constructs an SVG for the specified symbol, given as a SIDC, and returns it as a string for further processing. 42 | :param sidc: The SIDC to construct the symbol from 43 | :param bounding_padding: The padding around the symbol, in pixels, to maintain when cropping. Values less than 0 will result in no cropping being performed. The default value is 4 pixels. 44 | :param verbose: Whether to print ancillary information while processing, defaulting to false. 45 | :param use_variants: Whether to use variant symbols 46 | :param style: Style to use, between 'light', 'dark', 'medium', and 'unfilled' 47 | :param use_background: Whether to use a colored background around the symbol 48 | :param background_color: Background color to use, if it's used 49 | :return: A string containing the SVG for the constructed symbol. 50 | """ 51 | return get_svg_string(sidc, True, pixel_padding=bounding_padding, verbose=verbose, use_variants=use_variants, 52 | style=style, use_background=use_background, background_color=background_color, force_all_elements=force_all_elements) 53 | 54 | 55 | def get_symbol_svg_string_from_name(name_string:str, bounding_padding=4, verbose=False, use_variants=False, style='light', 56 | use_background:bool=True, background_color:str='#ffffff', force_all_elements=False, 57 | limit_to_symbol_sets=None) -> str: 58 | """ 59 | Constructs an SVG and returns it in string form, using the best guess as to the SIDC based on the provided name. 60 | :param name_string: The string containing the name, i.e. "Friendly infantry platoon headquarters" 61 | :param bounding_padding: The padding around the symbol, in pixels, to maintain when cropping. Values less than 0 will result in no cropping being performed. The default value is 4 pixels. 62 | :param verbose: Whether to print ancillary information while processing, defaulting to false. 63 | :param use_variants: Whether to use variant symbols 64 | :param style: Style to use, between 'light', 'dark', 'medium', and 'unfilled' 65 | :param use_background: Whether to use a colored background around the symbol 66 | :param background_color: Background color to use, if it's used 67 | :return: A string containing the SVG for the constructed symbol. 68 | """ 69 | return get_svg_string(name_string, False, pixel_padding=bounding_padding, verbose=verbose, use_variants=use_variants, style=style, 70 | use_background=use_background, background_color=background_color, force_all_elements=force_all_elements, 71 | limit_to_symbol_sets=limit_to_symbol_sets) 72 | 73 | 74 | def get_symbol_class(originator, is_sidc=True, verbose=False, limit_to_symbol_sets=None) -> Symbol: 75 | """ 76 | Returns an symbol.Symbol object representing a symbol constructed from the given name, as a best 77 | guess, or SIDC, depending on inputs 78 | :param originator: The variable to construct from, whether name or SIDC 79 | :param is_sidc: Whether the originator is a SIDC (true) or name (false) 80 | :param verbose: Whether to print ancillary information 81 | :return: The generated symbol 82 | """ 83 | return symbol_cache.get_symbol(originator, is_sidc=is_sidc, verbose=verbose, limit_to_symbol_sets=limit_to_symbol_sets) 84 | 85 | 86 | def get_symbol_class_from_name(name, verbose=False, limit_to_symbol_sets=None) -> Symbol: 87 | """ 88 | Returns a symbol.Symbol object representing a symbol constructed from the given name, as a best 89 | guess 90 | :param name: The name to construct the Symbol from using a best-guess algorithm 91 | :param verbose: Whether to print ancillary information 92 | :return: An symbol.Symbol object 93 | """ 94 | return get_symbol_class(name, is_sidc=False, verbose=verbose, limit_to_symbol_sets=limit_to_symbol_sets) 95 | 96 | 97 | def get_symbol_class_from_sidc(sidc, verbose=False) -> Symbol: 98 | """ 99 | Returns an symbol.Symbol object representing a symbol constructed from the given SIDC 100 | :param sidc: The SIDC to construct the Symbol from 101 | :param verbose: Whether to print ancillary information 102 | :return: An symbol.Symbol object 103 | """ 104 | return get_symbol_class(sidc, is_sidc=True, verbose=verbose) 105 | 106 | 107 | def get_svg_string(creator_var:str, is_sidc:bool, pixel_padding=4, use_variants=False, style='light', use_background=False, 108 | background_color='#ffffff', verbose=False, force_all_elements=False, limit_to_symbol_sets=None) -> str: 109 | """ 110 | Constructs an SVG for the specified symbol, given as a SIDC or name, and returns it as a string for further processing. 111 | :param creator_var: The SIDC or name to construct the symbol from 112 | :param is_sidc: Whether the creator value is a SIDC (true) or name (false) 113 | :param pixel_padding: The padding around the symbol, in pixels, to maintain when cropping. Values less than 0 will result in no cropping being performed. The default value is 4 pixels. 114 | :param verbose: Whether to print ancillary information while processing, defaulting to false. 115 | :param use_variants: Whether to use variant symbols 116 | :param style: Style to use, between 'light', 'dark', 'medium', and 'unfilled' 117 | :param use_background: Whether to use a colored background around the symbol 118 | :param background_color: Background color to use, if it's used 119 | :return: A string containing the SVG for the constructed symbol. 120 | """ 121 | return symbol_cache.get_svg_string(creator_var, is_sidc, padding=pixel_padding, style=style, 122 | use_variants=use_variants, 123 | use_background=use_background, 124 | background_color=background_color, 125 | create_if_missing=True, verbose=verbose, 126 | force_all_elements=force_all_elements, 127 | limit_to_symbol_sets=limit_to_symbol_sets) 128 | 129 | 130 | def get_symbol_and_svg_string(creator_var:str, is_sidc:bool, padding:int=4, style:str='light', use_variants:bool=False, use_background=False, background_color='#ffffff', 131 | verbose=False, force_all_elements=False, limit_to_symbol_sets=None) -> tuple: 132 | """ 133 | Returns a (Symbol, str) tuple containing the symbol and SVG string for the given creator value and style elements 134 | :param creator_var: The SIDC or name to construct the symbol from 135 | :param is_sidc: Whether the creator value is a SIDC (true) or name (false) 136 | :param padding: The padding around the symbol, in pixels, to maintain when cropping. Values less than 0 will result in no cropping being performed. The default value is 4 pixels. 137 | :param style: Style to use, between 'light', 'dark', 'medium', and 'unfilled' 138 | :param use_variants: Whether to use variant symbols 139 | :param use_background: Whether to use a colored background around the symbol 140 | :param background_color: Background color to use, if it's used 141 | :param verbose: Whether to print ancillary information while processing, defaulting to false. 142 | :return: A (Symbol, str) tuple containing the symbol and SVG for the constructed symbol. 143 | """ 144 | return symbol_cache.get_symbol_and_svg_string(creator_var, is_sidc, padding, style, use_variants, use_background=use_background, 145 | background_color=background_color, verbose=verbose, force_all_elements=force_all_elements, limit_to_symbol_sets=limit_to_symbol_sets) 146 | 147 | 148 | def write_symbol_svg_string(creator_var, is_sidc:bool, out_filepath, bounding_padding=4, auto_name=True, use_variants=False, style='light', 149 | use_background=False, background_color='#ffffff', verbose=False, force_all_elements=False, 150 | limit_to_symbol_sets=None) -> None: 151 | """ 152 | Internal helper function to write a symbol to the file specified. 153 | :param is_sidc: Whether the creator_var parameter is a SIDC or a name 154 | :param creator_var: The string to construct the symbol from, either a SIDC or a name 155 | :param out_filepath: The filepath to write to 156 | :param bounding_padding: The padding around the symbol, in pixels, to maintain when cropping. Values less than 0 will result in no cropping being performed. The default value is 4 pixels. 157 | :param auto_name: Whether to auto-name the file by SIDC in the directory specified by out_filepath, or use out_filepath directly. Defaults to true. 158 | :param use_variants: Whether to use variant symbols 159 | :param style: Style to use, between 'light', 'dark', 'medium', and 'unfilled' 160 | :param use_background: Whether to use a colored background around the symbol 161 | :param background_color: Background color to use, if it's used 162 | :param verbose: Whether to print ancillary information while processing, defaulting to false. 163 | """ 164 | symbol, svg_string = symbol_cache.get_symbol_and_svg_string(creator_var, is_sidc, 165 | padding=bounding_padding, style=style, 166 | use_variants=use_variants, 167 | use_background=use_background, 168 | background_color=background_color, 169 | verbose=verbose, 170 | force_all_elements=force_all_elements, 171 | limit_to_symbol_sets=limit_to_symbol_sets) 172 | 173 | if auto_name: 174 | out_dir = os.path.dirname(out_filepath) if os.path.isfile(out_filepath) else out_filepath 175 | out_filepath = os.path.join(out_dir, f"{symbol.get_sidc()}.svg") 176 | 177 | with open(out_filepath, 'w') as out_file: 178 | out_file.write(svg_string) 179 | 180 | 181 | def write_symbol_svg_string_from_sidc(sidc, out_filepath, bounding_padding=4, auto_name=True, verbose=False, use_variants=False, style='light', 182 | use_background=False, background_color='#ffffff', force_all_elements=False) -> None: 183 | """ 184 | Internal helper function to write a symbol constructed from the given SIDC to the given filepath. 185 | :param sidc: The SIDC to construct the symbol from 186 | :param out_filepath: The filepath to write to 187 | :param bounding_padding: The padding around the symbol, in pixels, to maintain when cropping. Values less than 0 will result in no cropping being performed. The default value is 4 pixels. 188 | :param auto_name: Whether to auto-name the file by SIDC in the directory specified by out_filepath, or use out_filepath directly. Defaults to true. 189 | :param verbose: Whether to print ancillary information while processing, defaulting to false. 190 | :param use_variants: Whether to use variant symbol styles. 191 | :param style: Style to use, between 'light', 'dark', 'medium', and 'unfilled' 192 | :param use_background: Whether to use a colored background around the symbol 193 | :param background_color: Background color to use, if it's used 194 | """ 195 | write_symbol_svg_string(sidc, True, out_filepath, bounding_padding, auto_name, use_variants=use_variants, style=style, 196 | use_background=use_background, background_color=background_color, verbose=verbose, force_all_elements=force_all_elements) 197 | 198 | 199 | def write_symbol_svg_string_from_name(name_string, out_filepath, bounding_padding=4, auto_name=True, verbose=False, use_variants=False, 200 | style='light', use_background=False, background_color='#ffffff', force_all_elements=False, 201 | limit_to_symbol_sets=None) -> None: 202 | """ 203 | Internal helper function to write a symbol constructed as a best guess from the given name to the given filepath. 204 | :param name_string: The name to construct the symbol from 205 | :param out_filepath: The filepath to write to 206 | :param bounding_padding: The padding around the symbol, in pixels, to maintain when cropping. Values less than 0 will result in no cropping being performed. The default value is 4 pixels. 207 | :param auto_name: Whether to auto-name the file by SIDC in the directory specified by out_filepath, or use out_filepath directly. Defaults to true. 208 | :param verbose: Whether to print ancillary information while processing, defaulting to false. 209 | :param use_variants: Whether to use variant symbol styles. Defaults to false. 210 | :param style: Style to use, between 'light', 'dark', 'medium', and 'unfilled' 211 | :param use_background: Whether to use a colored background around the symbol 212 | :param background_color: Background color to use, if it's used 213 | """ 214 | write_symbol_svg_string(name_string, False, out_filepath, bounding_padding, auto_name, use_variants=use_variants, style=style, 215 | use_background=use_background, background_color=background_color, verbose=verbose, force_all_elements=force_all_elements, 216 | limit_to_symbol_sets=limit_to_symbol_sets) 217 | 218 | 219 | class MyParser(argparse.ArgumentParser): 220 | def error(self, message): 221 | sys.stderr.write('error: %s\n' % message) 222 | self.print_help() 223 | sys.exit(2) 224 | 225 | def command_line_main(): 226 | # Get current working directory 227 | style_choices_args = STYLE_CHOICES.copy() 228 | style_choices_args.extend([i[0] for i in style_choices_args]) 229 | 230 | parser = MyParser(prog='military-symbol', description=f"Military symbol generator per NATO APP-6B (E) / MIL-STD-2525E standards, v{VERSION}") 231 | parser.add_argument('-o', '--output-dir', dest='output_dir', default='', 232 | help="Chooses an output directory (or file if not auto-naming exports)") 233 | parser.add_argument('-n', '--by-name', dest='by_name', action='store_const', const=True, default=False, 234 | help="Indicates inputs are names, not SIDC strings") 235 | parser.add_argument('-a', '--auto-name', dest='auto_name', action='store_const', const=True, default=False, 236 | help='Whether to auto-name outputs (only valid for one)') 237 | parser.add_argument('-v', '--verbose', dest='verbose', action='store_const', const=True, default=False, 238 | help="Whether to print ancillary information (pollutes STDOUT if you're using a pipe)") 239 | parser.add_argument('-p', '--padding', dest='padding', action='store', default=4, 240 | help="Select padding for output SVG; default is 4; values < 0 will not crop to fit content") 241 | parser.add_argument('-t', '--use-variants', dest='use_variants', action='store_const', const=True, default=False, 242 | help='Whether to use variant symbols if they exist') 243 | parser.add_argument('-s', '--style', dest='style_name', action='store', choices=style_choices_args, default='light', 244 | help='Symbol style to use to draw symbols; choices are {}'.format(', '.join(style_choices_args))) 245 | parser.add_argument('-b', '--use-background', dest='use_background', action='store_const', const=True, default=False, 246 | help='Whether to draw a background halo around the symbol') 247 | parser.add_argument('-c', '--background-color', dest='background_color', action='store', default='#ffffff', 248 | help='Background color to use, if it\'s used') 249 | parser.add_argument('-d', '--sidc-only', dest='sidc_only', action='store_const', const=True, default=False, 250 | help='Whether to only return the SIDC from the name') 251 | parser.add_argument('-l', '--force-all-elements', dest='force_all_elements', action='store_const', const=True, default=False, 252 | help='Whether to force using all elements even if they would overlap in a way that conflicts with the standard') 253 | parser.add_argument('-m', '--template', dest='template_filename', action='store', default='', 254 | help='A template JSON file; see example folder for details') 255 | parser.add_argument('-e', '--limit-to', dest='limit_to_symbol_sets', action='append', default=[], 256 | help='Limits to a specific symbol set for name guessing, like air, ground, surface, etc. Has no effect when using SIDCs. ' + 257 | 'Multiple symbol sets to choose from can be specified.') 258 | parser.add_argument('--version', dest='show_version', action='store_const', const=True, default=False, help="Show the version and exit.") 259 | parser.add_argument('inputs', nargs='*', default=[]) 260 | 261 | arguments = parser.parse_args() 262 | 263 | if arguments.show_version: 264 | print(VERSION) 265 | return 266 | 267 | if len(arguments.inputs) < 1: 268 | parser.print_help() 269 | sys.exit(2) 270 | 271 | if arguments.by_name and arguments.verbose: 272 | print('\tParsing by name; results may not be exact', file=sys.stderr) 273 | 274 | use_auto_name: bool = arguments.auto_name 275 | if len(arguments.inputs) > 1 and not arguments.auto_name and not arguments.sidc_only: 276 | print('More than one input; auto-naming anyway', file=sys.stderr) 277 | use_auto_name = True 278 | 279 | # Parse output directory 280 | output_dir: str = '' 281 | if arguments.output_dir != '': 282 | output_dir = os.path.realpath(arguments.output_dir) 283 | if os.path.isdir(output_dir): 284 | use_auto_name = True 285 | 286 | # Parse style choice to use 287 | style_name = arguments.style_name 288 | if style_name not in STYLE_CHOICES: 289 | style_name = [name for name in STYLE_CHOICES if name[0] == style_name[0]][0] 290 | 291 | if arguments.template_filename != '': 292 | add_symbol_template_set(arguments.template_filename) 293 | 294 | # Handle limiting to symbol sets 295 | limit_to_symbol_sets = [] 296 | # Add limited symbol sets 297 | if arguments.verbose: 298 | print('\tLimiting to symbol sets [' + ', '.join(arguments.limit_to_symbol_sets) + ']') 299 | 300 | for limit in arguments.limit_to_symbol_sets: 301 | limit_to_symbol_sets.append(limit) 302 | if len(limit_to_symbol_sets) < 1: 303 | limit_to_symbol_sets = None 304 | 305 | 306 | # Loop through remaining inputs and process t hem 307 | for input_arg in arguments.inputs: 308 | if arguments.verbose: 309 | print(f'\tParsing "{input_arg}": {arguments.verbose}', file=sys.stderr) 310 | 311 | if arguments.by_name: # Construct from names 312 | if arguments.sidc_only: 313 | # Print SIDCs only 314 | symbol_class = get_symbol_class_from_name(input_arg, verbose=arguments.verbose) 315 | if symbol_class is None: 316 | print(f'No symbol creatable for name \"{input_arg}\"; skipping', file=sys.stderr) 317 | else: 318 | print(f'{input_arg} -> {symbol_class.get_sidc()}' if arguments.verbose else f'{symbol_class.get_sidc()}') 319 | 320 | elif output_dir: 321 | # Write to an output directory 322 | write_symbol_svg_string_from_name(input_arg, 323 | out_filepath=output_dir, 324 | bounding_padding=arguments.padding, 325 | auto_name=use_auto_name, 326 | verbose=arguments.verbose, 327 | use_variants=arguments.use_variants, 328 | style=style_name, 329 | use_background=arguments.use_background, 330 | background_color=arguments.background_color, 331 | force_all_elements=arguments.force_all_elements, 332 | limit_to_symbol_sets=limit_to_symbol_sets) 333 | else: 334 | # Write SVG strings to stdout 335 | print(get_symbol_svg_string_from_name(input_arg, 336 | bounding_padding=arguments.padding, 337 | verbose=arguments.verbose, 338 | use_variants=arguments.use_variants, 339 | style=style_name, 340 | use_background=arguments.use_background, 341 | background_color=arguments.background_color, 342 | force_all_elements=arguments.force_all_elements, 343 | limit_to_symbol_sets=limit_to_symbol_sets)) 344 | else: # Construct from SIDC 345 | if output_dir != '': 346 | write_symbol_svg_string_from_sidc(input_arg, 347 | out_filepath=output_dir, 348 | bounding_padding=arguments.padding, 349 | auto_name=use_auto_name, 350 | verbose=arguments.verbose, 351 | use_variants=arguments.use_variants, 352 | style=style_name, 353 | use_background=arguments.use_background, 354 | background_color=arguments.background_color, 355 | force_all_elements=arguments.force_all_elements) 356 | else: 357 | print(get_symbol_svg_string_from_sidc(input_arg, bounding_padding=arguments.padding, 358 | verbose=arguments.verbose, 359 | use_variants=arguments.use_variants, 360 | style=style_name, 361 | use_background=arguments.use_background, 362 | background_color=arguments.background_color, 363 | force_all_elements=arguments.force_all_elements)) 364 | 365 | if __name__ == '__main__': 366 | command_line_main() -------------------------------------------------------------------------------- /src/military_symbol/constants_parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | 5 | from schema import * 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/military_symbol/font_rendering.py: -------------------------------------------------------------------------------- 1 | import freetype 2 | 3 | """ 4 | Freetype helper functions for rendering text as paths. 5 | Functions are adapted from freetype-py examples. 6 | """ 7 | 8 | SCALING:float = 64.0 9 | BASE_HEIGHT:float = 1.0 # 70.0 / 64.0 #64.0 # 90.0 10 | 11 | def move_to(a, ctx): 12 | scale_factor = BASE_HEIGHT # ctx[2] / BASE_HEIGHT 13 | ctx[0].append("M {},{}".format( 14 | ctx[3][0] + (ctx[1] + a.x) / SCALING * scale_factor, 15 | ctx[3][1] + (-a.y / SCALING) * scale_factor)) 16 | 17 | def line_to(a, ctx): 18 | scale_factor = BASE_HEIGHT # ctx[2] / BASE_HEIGHT 19 | ctx[0].append("L {},{}".format( 20 | ctx[3][0] + (ctx[1] + a.x) / SCALING * scale_factor, 21 | ctx[3][1] + -a.y / SCALING * scale_factor 22 | )) 23 | 24 | def conic_to(a, b, ctx): 25 | scale_factor = BASE_HEIGHT # ctx[2] / BASE_HEIGHT 26 | ctx[0].append("Q {},{} {},{}".format( 27 | ctx[3][0] + (ctx[1] + a.x) / SCALING * scale_factor, 28 | ctx[3][1] + -a.y / SCALING * scale_factor, 29 | ctx[3][0] + (ctx[1] + b.x) / SCALING * scale_factor, 30 | ctx[3][1] + -b.y / SCALING * scale_factor 31 | )) 32 | 33 | def cubic_to(a, b, c, ctx): 34 | scale_factor = BASE_HEIGHT # ctx[2] / BASE_HEIGHT 35 | ctx[0].append("C {},{} {},{} {},{}".format( 36 | ctx[3][0] + (ctx[1] + a.x) / SCALING * scale_factor, 37 | ctx[3][1] + -a.y / SCALING * scale_factor, 38 | ctx[3][0] + (ctx[1] + b.x) / SCALING * scale_factor, 39 | ctx[3][1] + -b.y / SCALING * scale_factor, 40 | ctx[3][0] + (ctx[1] + c.x) / SCALING * scale_factor, 41 | ctx[3][1] + -c.y / SCALING * scale_factor 42 | )) 43 | 44 | """ 45 | Font object for rendering 46 | """ 47 | class Font(object): 48 | def __init__(self, font_file, size): 49 | self.face = freetype.Face(font_file) 50 | self.face.set_pixel_sizes(0, size) 51 | 52 | # Align is ['middle', 'start', 'end']. 53 | # Returns a list of SVG paths (but without SVG formatting or XML elements,just 54 | # the content of the "d" attribute) 55 | def render_text(self, text, pos = (0, 0), fontsize = 30, align='middle') -> str: 56 | # Determine text width 57 | text_width = 0.0 58 | for char_index, char in enumerate(text): 59 | self.face.set_char_size(fontsize * int(SCALING)) # Freetype uses a height of 64 by default 60 | self.face.load_char(char, freetype.FT_LOAD_DEFAULT | freetype.FT_LOAD_NO_BITMAP) 61 | text_width += self.face.glyph.advance.x 62 | 63 | if align == 'start' or align == 'left': 64 | x_offset:float = 0.0 65 | elif align == 'end' or align == 'right': 66 | x_offset:float = -text_width 67 | else: 68 | x_offset:float = -text_width / 2 69 | 70 | paths = [] 71 | 72 | for char_index, char in enumerate(text): 73 | ctx = ([], x_offset, fontsize, pos) 74 | self.face.set_char_size(fontsize * int(SCALING)) # Freetype uses a height of 64 by default 75 | self.face.load_char(char, freetype.FT_LOAD_DEFAULT | freetype.FT_LOAD_NO_BITMAP) 76 | self.face.glyph.outline.decompose(ctx, 77 | move_to=move_to, 78 | line_to=line_to, 79 | conic_to=conic_to, 80 | cubic_to=cubic_to) 81 | paths.append(' '.join(ctx[0])) 82 | x_offset += self.face.glyph.advance.x 83 | 84 | return paths # ' '.join(paths) -------------------------------------------------------------------------------- /src/military_symbol/name_combiner.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | FIRST = ["PSYOP", "psychological operations", "military information support operations", "MISO"] 4 | SECOND = ['broadcast transmitter antenna', 'broadcast transmitter antennae'] 5 | swappable:bool = True 6 | 7 | if True: #__name__ == '__main__': 8 | first = [f for f in FIRST] 9 | second = [f for f in SECOND] 10 | 11 | for (item_set, new_set) in [(FIRST, first), (SECOND, second)]: 12 | for item in item_set: 13 | if '-' in item: 14 | new_set.append(re.sub(r'\-', '', item)) 15 | new_set.append(re.sub(r' ', '', item)) 16 | 17 | results = [] 18 | for item_1 in first: 19 | for item_2 in second: 20 | results.append(f'{item_1} {item_2}') 21 | if swappable: 22 | results.append(f'{item_2} {item_1}') 23 | 24 | results = list(set([re.sub(r'\s+', ' ', i).strip() for i in results])) 25 | 26 | print('[' + ', '.join([f'"{item}"' for item in results]) + '],') -------------------------------------------------------------------------------- /src/military_symbol/name_to_sidc.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import os 4 | import copy 5 | 6 | sys.path.append(os.path.dirname(__file__)) 7 | 8 | from thefuzz import fuzz 9 | from functools import cmp_to_key 10 | 11 | from symbol import Symbol 12 | from schema import Schema, SymbolSet 13 | from template import Template 14 | 15 | def split_into_words(in_str:str) -> list: 16 | """ 17 | Helper function splitting the given string into a list of words. Hyphens are considered to separate words. 18 | :param in_str: The string to split into words 19 | :return: A list of words in the given string 20 | """ 21 | in_str = re.sub(r'[/ \-\t\n]+', ' ', in_str).strip().lower() 22 | in_str = ''.join([c for c in in_str if c.isalpha() or c == ' ']) 23 | return [word.strip() for word in in_str.strip().split(' ') if len(word) > 0] 24 | 25 | def exact_match(a, b): 26 | if a.lower() == b.lower(): 27 | return True 28 | 29 | return False 30 | 31 | def fuzzy_match(schema, name_string, candidate_list, match_longest=True, verbose=False, print_candidates=False): 32 | """ 33 | Returns a list of closest candidates for the given name string from the provided list of candidates 34 | :param schema: The symbol schema to consider the candidates to be part of 35 | :param name_string: The string to match against 36 | :param candidate_list: A list of candidates containing name, names, or alt_names attributes to choose from 37 | :param match_longest: Whether to prefer matching the longest possible (most specific) match, or the shortest. Defaults to true (longest). 38 | :return: A list containing the closest-matched candidates 39 | """ 40 | 41 | name_string = name_string.lower().strip() 42 | 43 | # Step 1: calculate the number of words in candidate_list_of_lists 44 | matches = [] # Set of (candidate, weight) pairs 45 | candidate_name_list = [(name.lower().strip(), candidate) for candidate in candidate_list for name in candidate.names if (not hasattr(candidate, 'match_name') or candidate.match_name)] 46 | 47 | print_candidates = False 48 | if len(name_string) < 1: 49 | return None, None 50 | 51 | if print_candidates: 52 | print(f'Searching for \"{name_string}\" in {candidate_name_list}') 53 | 54 | # Return matching candidates 55 | matches = [(name, candidate) for (name, candidate) in candidate_name_list if name in name_string] 56 | 57 | if print_candidates: 58 | print(matches) 59 | 60 | # Handle exact matches 61 | for match in matches: 62 | if exact_match(name_string, match[0]): 63 | matches = [(name, cand) for (name, cand) in matches if exact_match(name, name_string)] 64 | break 65 | 66 | if print_candidates: 67 | print('An exact match was found') 68 | print(matches) 69 | 70 | # Handle 0 and 1-match weights 71 | if len(matches) < 1: 72 | return None, None 73 | elif len(matches) == 1: 74 | return matches[0][1], name_string.replace(matches[0][0], '').strip().replace(' ', ' ') 75 | 76 | # Apply fuzzy match score 77 | matches = [(name, cand, fuzz.partial_ratio(name, name_string)) for (name, cand) in matches] 78 | if print_candidates: 79 | print([(n, r) for (n, c, r) in matches]) 80 | 81 | def sort_func(a, b): 82 | score_a = fuzz.partial_ratio(a[0], name_string) 83 | score_b = fuzz.partial_ratio(b[0], name_string) 84 | 85 | ent_a = a[1] 86 | ent_b = b[1] 87 | 88 | def calc_match_weight(obj) -> bool: 89 | op = getattr(obj, 'get_match_weight', None) 90 | if callable(op): 91 | return obj.get_match_weight() 92 | else: 93 | return 0.0 94 | 95 | sym_set_a = a[1].symbol_set if hasattr(a[1], 'symbol_set') else None 96 | sym_set_b = b[1].symbol_set if hasattr(b[1], 'symbol_set') else None 97 | 98 | match_weight_a = calc_match_weight(ent_a) 99 | match_weight_b = calc_match_weight(ent_b) 100 | 101 | if score_a == score_b: 102 | if match_weight_a > match_weight_b: 103 | return -1 104 | elif match_weight_a < match_weight_b: 105 | return 1 106 | 107 | if len(a[0]) == len(b[0]): 108 | return 0 109 | 110 | return 1 if len(a[0]) < len(b[0]) else -1 111 | 112 | return 1 if score_a > score_b else -1 113 | 114 | matches = sorted(matches, key=cmp_to_key(sort_func)) 115 | if verbose and 'symbol_set' in dir(matches[0][1]): 116 | print('\tMatches ' + f"{[f'{entity.names[0]} ({entity.symbol_set}-{entity.id_code}) (score {score}, match weight {entity.get_match_weight()})' for (name, entity, score) in matches]}") 117 | 118 | return matches[0][1], name_string.replace(matches[0][0], '').strip().replace(' ', ' ') 119 | 120 | def symbol_set_from_name(schema, item, verbose:bool=False) -> SymbolSet: 121 | if item is None or schema is None: 122 | return None 123 | if isinstance(item, SymbolSet): 124 | return item 125 | 126 | if not isinstance(item, str): 127 | print(f"Can't parse symbol set from item {item}", file=sys.stderr) 128 | return None 129 | 130 | # Guess from names 131 | sym_set_candidates = schema.symbol_sets.values() 132 | sym_set, sym_set_name = fuzzy_match(schema, item, sym_set_candidates, verbose=verbose) 133 | return sym_set 134 | 135 | 136 | def name_to_symbol(name: str, schema:Schema, verbose: bool = False, limit_to_symbol_sets:list=[], templates:list=[]) -> Symbol: 137 | """ 138 | Function to return a NATOSymbol object from the provided name, using a best guess 139 | :param name: The string representing the name to construct a best-guess symbol from, e.g. "Friendly infantry platoon" 140 | :param schema: The symbol schema to use 141 | :param verbose: Whether to print ancillary information during execution; defaults to false. 142 | :param limit_to_symbol_sets: A list of symbol set objects or names to restrict guessing to 143 | :return: 144 | """ 145 | 146 | if not Schema: 147 | print('Schema must be provided for name_to_symbol', file=sys.stderr) 148 | return None 149 | 150 | templates_to_use = schema.templates + templates 151 | 152 | limit_to_symbol_sets = copy.copy(limit_to_symbol_sets) 153 | 154 | proc_name_string = name 155 | if verbose: 156 | print(f'Matching "{name}"') 157 | 158 | # Handle symbol categories 159 | symbol_set_tags = re.findall(r"\[([\w\d\s]+)\]", proc_name_string) 160 | if len(symbol_set_tags) > 0: 161 | if limit_to_symbol_sets is not None: 162 | limit_to_symbol_sets.extend([d.lower() for d in symbol_set_tags]) 163 | else: 164 | limit_to_symbol_sets = list([d.lower() for d in symbol_set_tags]) 165 | 166 | # Remove category tags 167 | proc_name_string = re.sub(r"\[([\w\d\s]+)\]", "", proc_name_string) 168 | proc_name_string = re.sub(r"\s+", " ", proc_name_string) 169 | 170 | if verbose and len(symbol_set_tags) > 0: 171 | print('\tIdentified tags ' + ", ".join([f'\"{d}\"' for d in symbol_set_tags]) + f" -> {proc_name_string}") 172 | 173 | # Handle restricting to specific symbol sets 174 | if limit_to_symbol_sets is not None and isinstance(limit_to_symbol_sets, list) and len(limit_to_symbol_sets) > 0: 175 | limit_to_symbol_sets = [symbol_set_from_name(schema, item) for item in limit_to_symbol_sets if symbol_set_from_name(schema, item) is not None] 176 | else: 177 | limit_to_symbol_sets = None 178 | 179 | if verbose and limit_to_symbol_sets is not None: 180 | print(f'\tLimiting to symbol sets {[e.names[0] for e in limit_to_symbol_sets]}') 181 | 182 | # Sanitize string 183 | proc_name_string = proc_name_string.lower() 184 | proc_name_string = re.sub('[ \t\n]+', ' ', proc_name_string).strip() 185 | 186 | if verbose: 187 | print(f'\tMatching "{proc_name_string}"') 188 | 189 | # Step 0: Check for templates 190 | template: template.Template = None 191 | template, new_name_string = fuzzy_match(schema, proc_name_string, templates_to_use, print_candidates=True) 192 | ret_symbol: Symbol = None 193 | 194 | if template is not None: 195 | proc_name_string = new_name_string 196 | if verbose: 197 | print(f"\tMatches template \"{template.names[0]}\" leaving \"{proc_name_string}\"; affiliation is {'not ' if template.affiliation_is_flexible else ''}fixed: {template.symbol}") 198 | 199 | ret_symbol = copy.copy(template.symbol) 200 | ret_symbol.symbol_set = template.symbol.symbol_set 201 | else: 202 | ret_symbol = Symbol() 203 | 204 | ret_symbol.schema = schema 205 | 206 | # Step 1: Detect standard identity 207 | if template is None or template.affiliation_is_flexible: 208 | affiliation, new_name_string = fuzzy_match(schema, proc_name_string.lower(), schema.affiliations.values(), match_longest=True) 209 | 210 | if affiliation is None: 211 | print("\tUnable to determine standard identity; assuming unknown") 212 | affiliation = [si for si in schema.affiliations.values() if si.names[0] == 'unknown'][0] 213 | else: 214 | proc_name_string = new_name_string 215 | 216 | if verbose: 217 | print(f'\tAssuming affiliation "{affiliation.names[0]}" leaving "{proc_name_string}"') 218 | 219 | ret_symbol.affiliation = affiliation 220 | 221 | # Assume context is reality 222 | ret_symbol.context = schema.contexts['0'] 223 | 224 | # Amplifier 225 | prerun_amplifier = False 226 | 227 | if template is None or template.amplifier_is_flexible: 228 | candidate_amplifiers = [amp for amp in schema.amplifiers.values() if amp.prerun] 229 | 230 | # Limit to given symbol sets 231 | if limit_to_symbol_sets is not None: 232 | candidate_amplifiers = [amp for amp in candidate_amplifiers if amp.applies_to_any_in_symbol_sets(limit_to_symbol_sets)] 233 | 234 | amplifier, new_name_string = fuzzy_match(schema, proc_name_string, candidate_amplifiers, match_longest=True) 235 | if amplifier is not None: 236 | proc_name_string = new_name_string 237 | if verbose: 238 | print(f'\tAssuming amplifier "{amplifier.names[0]}" leaving "{proc_name_string}"') 239 | prerun_amplifier = True 240 | 241 | ret_symbol.amplifier = amplifier 242 | 243 | 244 | # Entity type 245 | if template is None or template.entity_is_flexible: 246 | candidates = schema.get_flat_entities() 247 | 248 | # Limit to symbol sets 249 | if limit_to_symbol_sets is not None: 250 | candidates = [c for c in candidates if c.is_in_any_of_symbol_sets(limit_to_symbol_sets)] 251 | 252 | # Limit by amplifiers TODO 253 | if ret_symbol.amplifier is not None: 254 | if verbose: 255 | print(f"\tLimiting to symbol sets \"{[e.names[0] for e in ret_symbol.amplifier.get_applicable_symbol_sets(schema)]}\" due to amplifier \"{ret_symbol.amplifier.names[0]}\"") 256 | candidates = [c for c in candidates if ret_symbol.amplifier.applies_to_entity(c)] 257 | 258 | entity_type, new_name_string = fuzzy_match(schema, proc_name_string, candidates, match_longest=True, verbose=verbose) 259 | 260 | symbol_set = None 261 | if entity_type is None: 262 | print(f"\tWARNING: Unable to determine entity type from string \"{proc_name_string}\"; defaulting to land unit") 263 | if limit_to_symbol_sets is None or len(limit_to_symbol_sets) < 1: 264 | symbol_set = [set for set in schema.symbol_sets.values() if set.names[0] == 'land unit'][0] 265 | else: 266 | symbol_set = limit_to_symbol_sets[0] 267 | 268 | ret_symbol.entity = symbol_set.entities.get('000000', None) 269 | ret_symbol.symbol_set = symbol_set 270 | else: 271 | symbol_set = entity_type.symbol_set 272 | proc_name_string = new_name_string 273 | ret_symbol.symbol_set = symbol_set 274 | ret_symbol.entity = entity_type 275 | 276 | if verbose: 277 | name = entity_type.names[0] if len(entity_type.names) > 0 else '' 278 | print(f'\tAssuming entity "{name}" ({entity_type.id_code}) ' + 279 | f'from symbol set "{entity_type.symbol_set.names[0]}" leaving \"{proc_name_string}\"') 280 | 281 | # Amplifier post-run 282 | if (template is None or template.amplifier_is_flexible) and not prerun_amplifier: 283 | ret_symbol.amplifier = None 284 | candidate_amplifiers = [amp for amp in schema.amplifiers.values() if amp.applies_to_entity(ret_symbol.entity)] 285 | amplifier, new_name_string = fuzzy_match(schema, proc_name_string, candidate_amplifiers, match_longest=True) 286 | if amplifier is not None: 287 | proc_name_string = new_name_string 288 | ret_symbol.amplifier = amplifier 289 | 290 | # Double-check amplifier 291 | if prerun_amplifier and ret_symbol.amplifier is not None and not ret_symbol.amplifier.applies_to_symbol_set(ret_symbol.symbol_set): 292 | if not ret_symbol.amplifier.applies_to_symbol_set(ret_symbol.symbol_set): 293 | print('No symset apply') 294 | if not ret_symbol.amplifier.applies_to_dimension(ret_symbol.symbol_set.dimension): 295 | print(f'No dimension {ret_symbol.amplifier.applies_to}') 296 | print(f'Removing amplifier "{ret_symbol.amplifier.names[0]}" due to mismatch with symbol set {ret_symbol.symbol_set}') 297 | ret_symbol.amplifier = None 298 | 299 | if verbose: 300 | if ret_symbol.amplifier is not None: 301 | print(f'\tConfirming amplifier "{ret_symbol.amplifier.names[0]}" leaving "{proc_name_string}"') 302 | else: 303 | print('\tNo modifier assigned') 304 | 305 | # Find task force / headquarters / dummy 306 | if template is None or template.hqtfd_is_flexible: 307 | 308 | candidates = [hc for hc in schema.hqtfds.values() if not hc.matches_blacklist(proc_name_string) and hc.applies_to_symbol_set(ret_symbol.symbol_set)] 309 | 310 | hqtfd, new_name_string = fuzzy_match(schema, proc_name_string, 311 | [code for code in schema.hqtfds.values() if code in candidates], 312 | match_longest=True) 313 | if hqtfd is not None: 314 | proc_name_string = new_name_string 315 | if verbose: 316 | print(f'\tAssuming HQTFD code "{hqtfd.names[0]}" leaving "{proc_name_string}"') 317 | 318 | ret_symbol.hqtfd = hqtfd 319 | 320 | # Find status code 321 | if template is None or template.status_is_flexible: 322 | # print([status.names for status in symbol_schema.statuses.values()]) 323 | status_code, new_name_string = fuzzy_match(schema, proc_name_string, 324 | [code for code in schema.statuses.values()], 325 | match_longest=True) 326 | if status_code is not None: 327 | proc_name_string = new_name_string 328 | if verbose: 329 | print(f'\tAssuming status code "{status_code.names[0]}" leaving "{proc_name_string}"') 330 | 331 | ret_symbol.status = status_code 332 | 333 | # Find modifiers 334 | 335 | # Assemble options 336 | modifier_candidates = [] 337 | mod_candidate_sets = {} 338 | for mod_set in [1, 2]: 339 | if template is None or getattr(template, f'modifier_{mod_set}_is_flexible'): 340 | sym_set_mods = list(getattr(symbol_set, f'm{mod_set}').values()) 341 | modifier_candidates += sym_set_mods 342 | for c in sym_set_mods: 343 | mod_candidate_sets[c] = mod_set 344 | 345 | for extra_symbol_set in schema.symbol_sets.values(): 346 | if extra_symbol_set.common: 347 | modifier_candidates += list(getattr(extra_symbol_set, f'm{mod_set}').values()) 348 | for c in sym_set_mods: 349 | mod_candidate_sets[c] = mod_set 350 | 351 | # Pick the modifier 352 | mod, new_name_string = fuzzy_match(schema, proc_name_string, modifier_candidates, match_longest=True, print_candidates=True) 353 | 354 | if mod is not None: 355 | proc_name_string = new_name_string 356 | mod_set = mod_candidate_sets[mod] 357 | 358 | if verbose: 359 | print(f'\tAssuming modifier {mod_set} "{mod.names[0]}" ({mod.id_code}) from "{mod.symbol_set.names[0]}" leaving "{proc_name_string}"') 360 | 361 | setattr(ret_symbol, f'modifier_{mod_set}', mod) 362 | 363 | return ret_symbol 364 | 365 | if __name__ == '__main__': 366 | 367 | TEST_NAMES = [ 368 | 'friendly wheeled x infantry platoon', 369 | 'hostile amphibious artillery battery HQ', 370 | "joker network", 371 | "neutral MILCO general", 372 | "enemy attack planetary lander", 373 | "assumed friend space station", 374 | "friendly military base [land installation]", 375 | "neutral infantry battalion HQ unit", 376 | "hostile wheeled x MLRS artillery battalion" 377 | ] 378 | 379 | schema = Schema.load_from_directory() 380 | 381 | test_dir = os.path.join(os.path.dirname(__file__), '..', 'test') 382 | os.makedirs(test_dir, exist_ok=True) 383 | 384 | template_list = Template.load_from_file('example_template.yml', schema=schema) 385 | 386 | for name in TEST_NAMES: 387 | print(f'Testing "{name}"') 388 | symbol = name_to_symbol(name=name, schema=schema, verbose=True, templates=template_list) 389 | svg = symbol.get_svg() 390 | 391 | with open(os.path.join(test_dir, f'{name}.svg'), 'w') as out_file: 392 | out_file.write(svg) 393 | 394 | pass -------------------------------------------------------------------------------- /src/military_symbol/output_style.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class OutputStyle: 5 | """ 6 | Class for defining an output style for the generated C++ code 7 | """ 8 | 9 | DEFAULT_STROKE_WIDTH:float = 4.0 10 | DEFAULT_FILL_STYLE:str = 'light' 11 | DEFAULT_FONT_FILE:str = os.path.join(os.path.dirname(__file__), 'Roboto.ttf') 12 | DEFAULT_PADDING:float = 3.0 13 | DEFAULT_BACKGROUND_WIDTH:float = 16.0 14 | 15 | def __init__(self, use_text_paths:bool = False): 16 | self.use_text_paths:bool = use_text_paths 17 | self.text_path_font:str = OutputStyle.DEFAULT_FONT_FILE 18 | self.use_alternate_icons:bool = False 19 | self.fill_style:str = OutputStyle.DEFAULT_FILL_STYLE 20 | self.padding:str = OutputStyle.DEFAULT_PADDING 21 | self.background_color:str = "#ffffff" 22 | self.background_width:float = 0 23 | -------------------------------------------------------------------------------- /src/military_symbol/schema/air.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "01", 3 | "name": "air", 4 | "dimension": "air", 5 | "modcats": ["aircraft type", "mission area", "crime", "capability"], 6 | 7 | "IC": { 8 | "110000": { 9 | "names": ["military"], 10 | "icon": [{"text": "MIL"}] 11 | }, 12 | "110100": { 13 | "names": ["fixed wing"], 14 | "icon": [{"fill": true, "d": "m 75.1,90.3 19.6,0 0,-6.5 10.5,0 0,6.5 19.6,0 0,9.7 -19.6,0 0,16.2 6.5,0 0,3.2 -23.6,0 0,-3.2 6.5,0 0,-16.2 -19.6,0 z m 21.6,-6.5 0,-3.2 6.5,0 0,3.2"}] 15 | }, 16 | "110101": { 17 | "names": ["medevac"], 18 | "icon": [{"fill": true, "stroke": false, "d": "M93,83 l14,0 0,10 10,0 0,14 -10,0 0,10 -14,0 0,-10 -10,0 0,-14 10,0 Z"}] 19 | }, 20 | "110102": { 21 | "names": ["attack", "strike"], 22 | "icon": [{"text": "A"}] 23 | }, 24 | "110103": { 25 | "names": ["bomber"], 26 | "icon": [{"text": "B"}] 27 | }, 28 | "110104": { 29 | "names": ["fighter"], 30 | "icon": [{"text": "F"}] 31 | }, 32 | "110105": { 33 | "names": ["fighter/bomber"], 34 | "icon": [{"text": "F/B"}] 35 | }, 36 | "110107": { 37 | "names": ["cargo"], 38 | "icon": [{"text": "C"}] 39 | }, 40 | "110108": { 41 | "names": ["electronic combat", "EC", "jammer"], 42 | "icon": [{"text": "J"}] 43 | }, 44 | "110109": { 45 | "names": ["tanker"], 46 | "icon": [{"text": "K"}], 47 | "modcats": ["aircraft type", "mission area", "crime", "refueling capability"] 48 | }, 49 | "110110": { 50 | "names": ["patrol"], 51 | "icon": [{"text": "P"}] 52 | }, 53 | "110111": { 54 | "names": ["reconnaissance", "recon"], 55 | "icon": [{"text": "R"}] 56 | }, 57 | "110112": { 58 | "names": ["trainer"], 59 | "icon": [{"text": "T"}] 60 | }, 61 | "110113": { 62 | "names": ["utility"], 63 | "icon": [{"text": "U"}] 64 | }, 65 | "110114": { 66 | "names": ["vertical take-off and landing", "short take-off and landing", "STOL", "VTOL", "VSTOL"], 67 | "icon": [{"text": "V"}] 68 | }, 69 | "110115": { 70 | "names": ["airborne command post", "ACP"], 71 | "icon": [{"text": "ACP"}] 72 | }, 73 | "110116": { 74 | "names": ["airborne early warning", "AEW"], 75 | "icon": [{"text": "AEW"}] 76 | }, 77 | "110117": { 78 | "names": ["anti-surface warfare", "ASUW"], 79 | "icon": [{"text": "ASUW"}] 80 | }, 81 | "110118": { 82 | "names": ["anti-submarine warfare", "ASW"], 83 | "icon": [{"text": "ASW"}] 84 | }, 85 | "110119": { 86 | "names": ["communications"], 87 | "icon": [{"text": "COM"}] 88 | }, 89 | "110120": { 90 | "names": ["combat search and rescue", "CSAR"], 91 | "icon": [{"text": "CSAR"}] 92 | }, 93 | "110121": { 94 | "names": ["electronic support"], 95 | "icon": [{"text": "ES"}] 96 | }, 97 | "110122": { 98 | "names": ["government"], 99 | "icon": [{"text": "GOV"}] 100 | }, 101 | "110123": { 102 | "names": ["mine countermeasures", "MCM"], 103 | "icon": [{"text": "MCM"}] 104 | }, 105 | "110124": { 106 | "names": ["personnel recovery"], 107 | "icon": [{"text": "PR"}] 108 | }, 109 | "110125": { 110 | "names": ["search and rescue"], 111 | "icon": [{"text": "SAR"}] 112 | }, 113 | "110126": { 114 | "names": ["special operations forces", "SOF"], 115 | "icon": [{"text": "SOF"}] 116 | }, 117 | "110127": { 118 | "names": ["ultralight", "ultra light", "ultra-light"], 119 | "icon": [{"text": "UL"}] 120 | }, 121 | "110128": { 122 | "names": ["photographic reconnaissance", "photo recon"], 123 | "icon": [{"text": "PH"}] 124 | }, 125 | "110129": { 126 | "names": ["very important person", "VIP"], 127 | "icon": [{"text": "VIP"}] 128 | }, 129 | "110130": { 130 | "names": ["suppression of enemy air defense", "SEAD"], 131 | "icon": [{"text": "SEAD"}] 132 | }, 133 | "110131": { 134 | "names": ["passenger"], 135 | "icon": [{"text": "PX"}] 136 | }, 137 | "110132": { 138 | "names": ["escort"], 139 | "icon": [{"text": "E"}] 140 | }, 141 | "110133": { 142 | "names": ["electronic attack"], 143 | "icon": [{"text": "EA"}] 144 | }, 145 | 146 | 147 | "110200": { 148 | "names": ["rotary wing", "helicopter", "helo"], 149 | "icon": [{"fill": true, "d": "M60,85 l40,15 40,-15 0,30 -40,-15 -40,15 z"}] 150 | }, 151 | "110300": { 152 | "names": [ 153 | "unmanned aerial vehicle", "UAV", 154 | "unmanned aircraft system", "UAS", 155 | "unmanned aircraft", "remotely piloted vehicle", "RPV" 156 | ], 157 | "icon": [ 158 | {"fill": true, "stroke": false, "d": "m 60,84 40,20 40,-20 0,8 -40,25 -40,-25 z"} 159 | ] 160 | }, 161 | "110400": { 162 | "names": ["vertical takeoff UAV"], 163 | "icon": [ 164 | {"fill": true, "d": "m 70,85 30,10 30,-10 0,-5 -30,5 -30,-5 z m -10,5 40,15 40,-15 0,30 -40,-15 -40,15 z"} 165 | ] 166 | }, 167 | "110500": { 168 | "names": ["lighter than air", "balloon", "aerostat"], 169 | "icon": [ 170 | {"r": 15, "pos": [100, 95], "fill": true}, 171 | {"fill": true, "d": "M95,110 l0,10 10,0 0,-10 z"} 172 | ] 173 | }, 174 | "110600": { 175 | "names": ["airship"], 176 | "icon": [{"fill": true, "d": "m 110,110 10,10 10,0 -5,-15 m 0,-10 5,-15 -10,0 -10,10 m 17.2,10 c 0,6.1 -12.2,11.1 -27.2,11.1 -15,0 -27.2,-5 -27.2,-11.1 0,-6.1 12.2,-11.1 27.2,-11.1 15,0 27.2,5 27.2,11.1 z"}] 177 | }, 178 | "110700": { 179 | "names": ["tethered lighter than air", "tethered balloon"], 180 | "icon": [{"fill": true, "d": "M 75,110 85,95 m -5,20 c 0,2.8 -2.2,5 -5,5 -2.8,0 -5,-2.2 -5,-5 0,-2.8 2.2,-5 5,-5 2.8,0 5,2.2 5,5 z m 15,-6 0,11 10,0 0,-11 m 10,-14 c 0,8.3 -6.7,15 -15,15 -8.3,0 -15,-6.7 -15,-15 0,-8.3 6.7,-15 15,-15 8.3,0 15,6.7 15,15 z"}] 181 | }, 182 | 183 | 184 | "120000": { 185 | "names": ["civilian"], 186 | "civ": true, 187 | "icon": [{"text": "CIV", "stroke": true, "fill": "white"}] 188 | }, 189 | "120100": { 190 | "names": ["civilian fixed wing"], 191 | "civ": true, 192 | "icon": [{"fill": "white", "stroke": true, "d": "m 75.1,90.3 19.6,0 0,-6.5 10.5,0 0,6.5 19.6,0 0,9.7 -19.6,0 0,16.2 6.5,0 0,3.2 -23.6,0 0,-3.2 6.5,0 0,-16.2 -19.6,0 z m 21.6,-6.5 0,-3.2 6.5,0 0,3.2"}] 193 | }, 194 | "120200": { 195 | "names": ["civilian rotary wing"], 196 | "civ": true, 197 | "icon": [{"fill": "white", "stroke": true, "d": "M60,85 l40,15 40,-15 0,30 -40,-15 -40,15 z"}] 198 | }, 199 | "120300": { 200 | "names": [ 201 | "civilian unmanned aerial vehicle", "civilian UAV", 202 | "civilian unmanned aircraft system", "civilian UAS", 203 | "civilian unmanned aircraft", "civilian remotely piloted vehicle", "civilian RPV" 204 | ], 205 | "civ": true, 206 | "icon": [ 207 | {"fill": "white", "stroke": true, "d": "m 60,84 40,20 40,-20 0,8 -40,25 -40,-25 z"} 208 | ] 209 | }, 210 | "120400": { 211 | "names": ["civilian lighter than air", "civilian balloon", "civilian aerostat"], 212 | "civ": true, 213 | "icon": [ 214 | {"r": 15, "pos": [100, 95], "fill": "white"}, 215 | {"fill": "white", "d": "M95,110 l0,10 10,0 0,-10 z"} 216 | ] 217 | }, 218 | "120500": { 219 | "names": ["civilian airship"], 220 | "civ": true, 221 | "icon": [{"fill": "white", "d": "m 110,110 10,10 10,0 -5,-15 m 0,-10 5,-15 -10,0 -10,10 m 17.2,10 c 0,6.1 -12.2,11.1 -27.2,11.1 -15,0 -27.2,-5 -27.2,-11.1 0,-6.1 12.2,-11.1 27.2,-11.1 15,0 27.2,5 27.2,11.1 z"}] 222 | }, 223 | "120600": { 224 | "names": ["civilian tethered lighter than air", "civilian tethered balloon"], 225 | "civ": true, 226 | "icon": [{"fill": "white", "d": "M 75,110 85,95 m -5,20 c 0,2.8 -2.2,5 -5,5 -2.8,0 -5,-2.2 -5,-5 0,-2.8 2.2,-5 5,-5 2.8,0 5,2.2 5,5 z m 15,-6 0,11 10,0 0,-11 m 10,-14 c 0,8.3 -6.7,15 -15,15 -8.3,0 -15,-6.7 -15,-15 0,-8.3 6.7,-15 15,-15 8.3,0 15,6.7 15,15 z"}] 227 | }, 228 | 229 | "130000": { 230 | "names": ["weapon"], 231 | "match weight": -1, 232 | "icon": [{"text": "WPN"}] 233 | }, 234 | "130100": { 235 | "names": ["bomb"], 236 | "icon": [{"text": "BOMB"}] 237 | }, 238 | "130200": { 239 | "names": ["decoy"], 240 | "icon": [{"fill": true, "stroke": false, "d": "M 85 81 L 65 98 L 85 119 L 85 81 z M 110 81 L 90 98 L 110 119 L 110 81 z M 135 81 L 115 98 L 135 119 L 135 81 z"}] 241 | }, 242 | "140000": { 243 | "names": ["manual track"], 244 | "icon": [{"text": "MAN"}] 245 | } 246 | }, 247 | "M1": { 248 | "01": { 249 | "names": ["attack"], 250 | "until": "2525E", 251 | "new": "C-1-106", 252 | "cat": "aircraft type", 253 | "icon": [{"textm1": "A"}] 254 | }, 255 | "02": { 256 | "names": ["bomber"], 257 | "cat": "aircraft type", 258 | "icon": [{"textm1": "B"}] 259 | }, 260 | "03": { 261 | "names": ["cargo"], 262 | "until": "2525E", 263 | "new": "C-1-110", 264 | "cat": "aircraft type", 265 | "icon": [{"textm1": "C"}] 266 | }, 267 | "04": { 268 | "names": ["fighter"], 269 | "cat": "aircraft type", 270 | "icon": [{"textm1": "D"}] 271 | }, 272 | "05": { 273 | "names": ["interceptor"], 274 | "until": "2525E", 275 | "cat": "aircraft type", 276 | "icon": [{"textm1": "I"}] 277 | }, 278 | "06": { 279 | "names": ["tanker"], 280 | "cat": "aircraft type", 281 | "icon": [{"textm1": "K"}] 282 | }, 283 | "07": { 284 | "names": ["utility"], 285 | "until": "2525E", 286 | "new": "C-1-111", 287 | "cat": "aircraft type", 288 | "icon": [{"textm1": "U"}] 289 | }, 290 | "08": { 291 | "names": ["VTOL"], 292 | "until": "2525E", 293 | "new": "C-1-105", 294 | "cat": "aircraft type", 295 | "icon": [{"textm1": "V"}] 296 | }, 297 | "09": { 298 | "names": ["passenger"], 299 | "cat": "aircraft type", 300 | "icon": [{"textm1": "PX"}] 301 | }, 302 | "10": { 303 | "names": ["ultralight"], 304 | "cat": "aircraft type", 305 | "icon": [{"textm1": "UL"}] 306 | }, 307 | "11": { 308 | "names": ["airborne command post", "ACP"], 309 | "cat": "aircraft type", 310 | "icon": [{"textm1": "ACP"}] 311 | }, 312 | "12": { 313 | "names": ["airborne early warning", "AEW"], 314 | "cat": "aircraft type", 315 | "icon": [{"textm1": "AEW"}] 316 | }, 317 | "13": { 318 | "names": ["government"], 319 | "until": "2525E", 320 | "new": "C-1-144", 321 | "cat": "mission area", 322 | "icon": [{"textm1": "GOV"}] 323 | }, 324 | "14": { 325 | "names": ["medevac"], 326 | "until": "2525E", 327 | "new": "C-1-127", 328 | "cat": "mission area", 329 | "icon": [{"fill": true, "stroke": false, "d": "M95.5,80 l9,0 0,-9 9,0 0,-9 -9,0 0,-9 -9,0 0,9 -9,0 0,9 9,0 Z"}] 330 | }, 331 | "15": { 332 | "names": ["escort"], 333 | "new": "C-1-152", 334 | "until": "2525E", 335 | "cat": "mission area", 336 | "icon": [{"textm1": "E"}] 337 | }, 338 | "16": { 339 | "names": ["jammer", "electronic combat"], 340 | "cat": "mission area", 341 | "icon": [{"textm1": "J"}] 342 | }, 343 | "17": { 344 | "names": ["patrol"], 345 | "cat": "mission area", 346 | "icon": [{"textm1": "P"}] 347 | }, 348 | "18": { 349 | "names": ["reconnaissance", "recon"], 350 | "cat": "mission area", 351 | "icon": [{"textm1": "R"}] 352 | }, 353 | "19": { 354 | "names": ["trainer"], 355 | "cat": "mission area", 356 | "icon": [{"textm1": "T"}] 357 | }, 358 | "20": { 359 | "names": ["photographic reconnaissance", "photo"], 360 | "cat": "mission area", 361 | "icon": [{"textm1": "PH"}] 362 | }, 363 | "21": { 364 | "names": ["personnel recovery"], 365 | "cat": "mission area", 366 | "icon": [{"textm1": "PR"}] 367 | }, 368 | "22": { 369 | "names": ["anti-submarine warfare", "ASW"], 370 | "cat": "mission area", 371 | "icon": [{"textm1": "ASW"}] 372 | }, 373 | "23": { 374 | "names": ["communications"], 375 | "cat": "mission area", 376 | "icon": [{"textm1": "COM"}] 377 | }, 378 | "24": { 379 | "names": ["electronic support"], 380 | "cat": "mission area", 381 | "icon": [{"textm1": "ES"}] 382 | }, 383 | "25": { 384 | "names": ["mine countermeasures", "MCM"], 385 | "until": "2525E", 386 | "new": "C-1-153", 387 | "cat": "mission area", 388 | "icon": [{"textm1": "MCM"}] 389 | }, 390 | "26": { 391 | "names": ["search and rescue", "SAR"], 392 | "until": "2525E", 393 | "new": "C-1-128", 394 | "cat": "mission area", 395 | "icon": [{"textm1": "SAR"}] 396 | }, 397 | "27": { 398 | "names": ["special operations forces", "SOF"], 399 | "until": "2525E", 400 | "new": "C-1-131", 401 | "cat": "mission area", 402 | "icon": [{"textm1": "SOF"}] 403 | }, 404 | "28": { 405 | "names": ["surface warfare"], 406 | "until": "2525E", 407 | "new": "C-1-155", 408 | "cat": "mission area", 409 | "icon": [{"textm1": "SUW"}] 410 | }, 411 | "29": { 412 | "names": ["VIP transport", "very important person transport"], 413 | "cat": "mission area", 414 | "icon": [{"textm1": "VIP"}] 415 | }, 416 | "30": { 417 | "names": ["combat search and rescue", "CSAR"], 418 | "cat": "mission area", 419 | "icon": [{"textm1": "CSAR"}] 420 | }, 421 | "31": { 422 | "names": ["suppression of enemy air defense", "SEAD"], 423 | "until": "2525E", 424 | "cat": "mission area", 425 | "icon": [{"textm1": "SEAD"}] 426 | }, 427 | "32": { 428 | "names": ["antisurface warfare"], 429 | "cat": "mission area", 430 | "icon": [{"textm1": "ASUW"}] 431 | }, 432 | "33": { 433 | "names": ["fighter/bomber"], 434 | "cat": "mission area", 435 | "icon": [{"textm1": "F/B"}] 436 | }, 437 | "34": { 438 | "names": ["intensive care"], 439 | "cat": "mission area", 440 | "icon": [{"textm1": "IC"}] 441 | }, 442 | "35": { 443 | "names": ["electronic attack", "EA", "electromagnetic attack"], 444 | "cat": "mission area", 445 | "icon": [{"textm1": "EA"}] 446 | }, 447 | "36": { 448 | "names": ["multi-mission"], 449 | "cat": "mission area", 450 | "icon": [{"textm1": "MM"}] 451 | }, 452 | "37": { 453 | "names": ["hijacking", "hijacked"], 454 | "until": "2525E", 455 | "new": "C-1-150", 456 | "cat": "crime", 457 | "icon": [{"textm1": "H"}] 458 | }, 459 | "38": { 460 | "names": ["ASW helo - LAMPS"], 461 | "cat": "mission area", 462 | "icon": [{"textm1": "LP"}] 463 | }, 464 | "39": { 465 | "names": ["ASW helo - SH-60R"], 466 | "cat": "mission area", 467 | "icon": [{"textm1": "60R"}] 468 | }, 469 | "40": { 470 | "names": ["hijacker"], 471 | "until": "2525E", 472 | "new": "C-1-150", 473 | "cat": "capability", 474 | "icon": [{"textm1": "HJ"}] 475 | }, 476 | "41": { 477 | "names": ["cyberspace"], 478 | "until": "2525E", 479 | "new": "C-1-115", 480 | "cat": "capability", 481 | "icon": [{"textm1": "CYB"}] 482 | } 483 | }, 484 | "M2": { 485 | "01": { 486 | "names": ["heavy"], 487 | "until": "2525E", 488 | "new": "C-2-118", 489 | "cat": "capability", 490 | "icon": [{"textm2": "H"}] 491 | }, 492 | "02": { 493 | "names": ["medium"], 494 | "until": "2525E", 495 | "new": "C-2-119", 496 | "cat": "capability", 497 | "icon": [{"textm2": "M"}] 498 | }, 499 | "03": { 500 | "names": ["light"], 501 | "until": "2525E", 502 | "new": "C-2-121", 503 | "cat": "capability", 504 | "icon": [{"textm2": "L"}] 505 | }, 506 | "04": { 507 | "names": ["boom-only"], 508 | "cat": "refueling capability", 509 | "icon": [{"textm2": "B"}] 510 | }, 511 | "05": { 512 | "names": ["drogue-only"], 513 | "cat": "refueling capability", 514 | "icon": [{"textm2": "D"}] 515 | }, 516 | "06": { 517 | "names": ["boom and drogue"], 518 | "cat": "refueling capability", 519 | "icon": [{"textm2": "B/D"}] 520 | }, 521 | "07": { 522 | "names": ["close range"], 523 | "until": "2525E", 524 | "new": "C-2-117", 525 | "cat": "capability", 526 | "icon": [{"textm2": "CR"}] 527 | }, 528 | "08": { 529 | "names": ["short range"], 530 | "until": "2525E", 531 | "new": "C-2-116", 532 | "cat": "capability", 533 | "icon": [{"textm2": "SR"}] 534 | }, 535 | "09": { 536 | "names": ["medium range"], 537 | "until": "2525E", 538 | "new": "C-2-115", 539 | "cat": "capability", 540 | "icon": [{"textm2": "MR"}] 541 | }, 542 | "10": { 543 | "names": ["long range"], 544 | "until": "2525E", 545 | "new": "C-2-114", 546 | "cat": "capability", 547 | "icon": [{"textm2": "LR"}] 548 | }, 549 | "11": { 550 | "names": ["downlinked"], 551 | "cat": "track link availability", 552 | "icon": [{"textm2": "DL"}] 553 | }, 554 | "12": { 555 | "names": ["cyberspace"], 556 | "until": "2525E", 557 | "new": "C-2-122", 558 | "cat": "capability", 559 | "icon": [{"textm2": "CYB"}] 560 | } 561 | } 562 | } -------------------------------------------------------------------------------- /src/military_symbol/schema/air_missile.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "02", 3 | "name": "air missile", 4 | "dimension": "air", 5 | "IC": { 6 | "110000": { 7 | "names": ["missile"], 8 | "modcats": ["launch origin", "missile class", "missile destination", "missile status", "missile type", "missile range"], 9 | "icon": [ 10 | {"d": "M90,135 l0,-10 5,-5 0,-55 5,-5 5,5 0,55 5,5 0,10 -10,-10 z", "fill": "yellow"} 11 | ] 12 | } 13 | }, 14 | "M1": { 15 | "01": { 16 | "names": ["air"], 17 | "cat": "launch origin", 18 | "icon": [{"text": "A", "pos": [68, 110], "fontsize": 30}] 19 | }, 20 | "02": { 21 | "names": ["surface"], 22 | "cat": "launch origin", 23 | "icon": [{"text": "S", "pos": [68, 110], "fontsize": 30}] 24 | }, 25 | "03": { 26 | "names": ["subsurface"], 27 | "cat": "launch origin", 28 | "icon": [ 29 | {"text": "S", "pos": [68, 95], "fontsize": 30}, 30 | {"text": "U", "pos": [68, 125], "fontsize": 30} 31 | ] 32 | }, 33 | "04": { 34 | "names": ["space"], 35 | "cat": "launch origin", 36 | "icon": [ 37 | {"text": "S", "pos": [68, 95], "fontsize": 30}, 38 | {"text": "P", "pos": [68, 125], "fontsize": 30} 39 | ] 40 | }, 41 | "05": { 42 | "names": ["anti-ballistic"], 43 | "cat": "missile class", 44 | "icon": [ 45 | {"text": "A", "pos": [68, 95], "fontsize": 30}, 46 | {"text": "B", "pos": [68, 125], "fontsize": 30} 47 | ] 48 | }, 49 | "06": { 50 | "names": ["ballistic"], 51 | "cat": "missile class", 52 | "icon": [{"text": "B", "pos": [68, 110], "fontsize": 30}] 53 | }, 54 | "07": { 55 | "names": ["cruise"], 56 | "cat": "missile class", 57 | "icon": [{"text": "C", "pos": [68, 110], "fontsize": 30}] 58 | }, 59 | "08": { 60 | "names": ["interceptor"], 61 | "cat": "missile class", 62 | "icon": [{"text": "I", "pos": [68, 110], "fontsize": 30}] 63 | }, 64 | "08": { 65 | "names": ["hypersonic"], 66 | "cat": "missile class", 67 | "since": "2525E", 68 | "icon": [ 69 | {"text": "H", "pos": [68, 95], "fontsize": 30}, 70 | {"text": "V", "pos": [68, 125], "fontsize": 30} 71 | ] 72 | } 73 | }, 74 | "M2": { 75 | "01": { 76 | "names": ["air"], 77 | "cat": "missile destination", 78 | "icon": [{"text": "A", "pos": [132, 110], "fontsize": 30}] 79 | }, 80 | "02": { 81 | "names": ["surface"], 82 | "cat": "missile destination", 83 | "icon": [{"text": "S", "pos": [132, 110], "fontsize": 30}] 84 | }, 85 | "03": { 86 | "names": ["subsurface"], 87 | "cat": "missile destination", 88 | "icon": [ 89 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 90 | {"text": "U", "pos": [132, 125], "fontsize": 30} 91 | ] 92 | }, 93 | "04": { 94 | "names": ["space"], 95 | "cat": "missile destination", 96 | "icon": [ 97 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 98 | {"text": "P", "pos": [132, 125], "fontsize": 30} 99 | ] 100 | }, 101 | "05": { 102 | "names": ["launched"], 103 | "cat": "missile status", 104 | "icon": [{"text": "L", "pos": [132, 110], "fontsize": 30}] 105 | }, 106 | "06": { 107 | "names": ["missile"], 108 | "until": "2525E", 109 | "cat": "missile type", 110 | "icon": [{"text": "M", "pos": [132, 110], "fontsize": 30}] 111 | }, 112 | "07": { 113 | "names": ["Patriot"], 114 | "cat": "missile type", 115 | "icon": [{"text": "P", "pos": [132, 110], "fontsize": 30}] 116 | }, 117 | "08": { 118 | "names": ["Standard Missile-2", "SM-2"], 119 | "cat": "missile type", 120 | "icon": [ 121 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 122 | {"text": "2", "pos": [132, 125], "fontsize": 30} 123 | ] 124 | }, 125 | "09": { 126 | "names": ["Standard Missile-6", "SM-6"], 127 | "cat": "missile type", 128 | "icon": [ 129 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 130 | {"text": "6", "pos": [132, 125], "fontsize": 30} 131 | ] 132 | }, 133 | "10": { 134 | "names": ["Evolved Sea Sparrow Missile", "Sea Sparrow", "ESSM"], 135 | "cat": "missile type", 136 | "icon": [ 137 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 138 | {"text": "S", "pos": [132, 125], "fontsize": 30} 139 | ] 140 | }, 141 | "11": { 142 | "names": ["Rolling Airframe Missile", "RAM"], 143 | "cat": "missile type", 144 | "icon": [{"text": "R", "pos": [132, 110], "fontsize": 30}] 145 | }, 146 | "12": { 147 | "names": ["short range"], 148 | "cat": "missile range", 149 | "icon": [ 150 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 151 | {"text": "R", "pos": [132, 125], "fontsize": 30} 152 | ] 153 | }, 154 | "13": { 155 | "names": ["medium range"], 156 | "cat": "missile range", 157 | "icon": [ 158 | {"text": "M", "pos": [132, 95], "fontsize": 30}, 159 | {"text": "R", "pos": [132, 125], "fontsize": 30} 160 | ] 161 | }, 162 | "14": { 163 | "names": ["intermediate range"], 164 | "cat": "missile range", 165 | "icon": [ 166 | {"text": "I", "pos": [132, 95], "fontsize": 30}, 167 | {"text": "R", "pos": [132, 125], "fontsize": 30} 168 | ] 169 | }, 170 | "15": { 171 | "names": ["long range"], 172 | "cat": "missile range", 173 | "icon": [ 174 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 175 | {"text": "R", "pos": [132, 125], "fontsize": 30} 176 | ] 177 | }, 178 | "16": { 179 | "names": ["intercontinental"], 180 | "cat": "missile range", 181 | "icon": [ 182 | {"text": "I", "pos": [132, 95], "fontsize": 30}, 183 | {"text": "C", "pos": [132, 125], "fontsize": 30} 184 | ] 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /src/military_symbol/schema/common_modifiers.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "C", 3 | "name": "common modifiers", 4 | "common": true, 5 | "IC": {}, 6 | "match weight": -1, 7 | "M1": { 8 | "100": { 9 | "names": ["uav", "unmanned aerial vehicle", "uas", "unmanned aerial system", "remotely piloted vehicle", "rpv"], 10 | "cat": "mobility", 11 | "icon": [{"d": "m 80,65 20,13 20,-13 0,-5 -20,10 -20,-10 z", "fill": true, "stroke": false}] 12 | }, 13 | "101": { 14 | "names": ["robotic", "robot"], 15 | "cat": "mobility", 16 | "icon": [{"fill": true, "stroke": false, "d": "m 100,52.7 14.9,14.8 c 0.4,-0.3 0.9,-0.4 1.4,-0.4 1.5,0 2.7,1.2 2.7,2.7 0,1.4 -1.2,2.7 -2.7,2.7 -1.5,0 -2.7,-1.3 -2.7,-2.7 0,-0.4 0.1,-0.7 0.2,-1 l -10.4,-5.2 -2.5,8.6 c 0.2,0.1 0.4,0.2 0.6,0.3 0.7,0.5 1.2,1.3 1.2,2.3 0,1.5 -1.2,2.7 -2.7,2.7 -0.55,0 -1.06,-0.2 -1.49,-0.5 -0.73,-0.4 -1.22,-1.3 -1.22,-2.2 0,-1.2 0.77,-2.2 1.85,-2.6 l -2.53,-8.6 -10.42,5.2 c 0.12,0.3 0.18,0.6 0.18,1 0,1.5 -1.21,2.7 -2.7,2.7 -1.49,0 -2.7,-1.2 -2.7,-2.7 0,-1.5 1.21,-2.7 2.7,-2.7 0.52,0 1.01,0.1 1.42,0.4 l 14.9,-14.8 0,0 0,0 z"}] 17 | }, 18 | "102": { 19 | "names": ["fixed wing", "FW"], 20 | "cat": "mobility", 21 | "icon": [{"stroke": true, "fill": true, "d": "m 100,70 22,-8.7 c 11,0 11,17.4 0,17.4 L 100,70 78,78.7 c -10.9,0 -10.9,-17.4 0,-17.4 z"}], 22 | "notes": "Not used by USAF" 23 | }, 24 | "103": { 25 | "names": ["rotary wing", "RW"], 26 | "cat": "mobility", 27 | "icon": [{"stroke": true, "fill": true, "d": "m 75,60 0,15 50,-15 0,15 z"}] 28 | }, 29 | "104": { 30 | "names": ["tilt-rotor", "tiltrotor"], 31 | "cat": "mobility", 32 | "icon": [{"textm1": "TR"}] 33 | }, 34 | "105": { 35 | "names": ["VTOL", "vertical takeoff and landing", "vertical or short takeoff and landing", "VSTOL", "helicopter-equipped", "helicopter equipped"], 36 | "cat": "capability", 37 | "icon": [{"textm1": "VTOL"}] 38 | }, 39 | "106": { 40 | "names": ["attack", "strike"], 41 | "cat": "capability", 42 | "icon": [{"textm1": "A"}] 43 | }, 44 | "107": { 45 | "names": ["armored"], 46 | "cat": "capability", 47 | "icon": [{"d": "m 90,60 20,0 c 10,0 10,15 0,15 L 90,75 C 80,75 80,60 90,60"}] 48 | }, 49 | "108": { 50 | "names": ["ballistic missile", "ballistic missile defense"], 51 | "cat": "capability", 52 | "icon": [{"textm1": "BM"}] 53 | }, 54 | "109": { 55 | "names": ["bridge", "bridging"], 56 | "cat": "capability", 57 | "icon": [{"d": "m 121,78 -7,-6 H 86 l -7,6 m 42,-18 -7,7 H 86 l -7,-7"}] 58 | }, 59 | "110": { 60 | "names": ["cargo"], 61 | "cat": "capability", 62 | "icon": [{"d": "m 100,60 0,15 -15,0 0,-15 30,0 0,15 -15,0"}] 63 | }, 64 | "111": {"names": ["utility"], "cat": "capability", "icon": [{"textm1": "U"}]}, 65 | "112": {"names": ["light"], "cat": "capability", "icon": [{"textm1": "L"}]}, 66 | "113": {"names": ["medium"], "cat": "capability", "icon": [{"textm1": "M"}]}, 67 | "114": {"names": ["heavy"], "cat": "capability", "icon": [{"textm1": "H"}]}, 68 | "115": {"names": ["cyberspace", "cyber"], "cat": "capability", "icon": [{"textm1": "CYB"}]}, 69 | "116": {"names": ["command post node", "CPN"], "cat": "capability", "icon": [{"textm1": "CPN"}]}, 70 | "117": {"names": ["joint network node", "JNN"], "cat": "capability", "icon": [{"textm1": "JNN"}]}, 71 | "118": {"names": ["retransmission site", "retrans site"], "cat": "capability", "icon": [{"textm1": "RTNS"}]}, 72 | "119": {"names": ["brigade"], "cat": "support level", "icon": [{"d": "m 107.5,62.5 -15,15 m 0,-15 15,15"}]}, 73 | "120": {"names": ["close protection"], "cat": "capability", "icon": [{"textm1": "CLP"}]}, 74 | 75 | "121": {"names": ["combat"], "cat": "capability", "icon": [{"textm1": "CBT"}]}, 76 | 77 | "122": {"names": ["command and control", "C2"], "cat": "capability", "icon": [{"textm1": "C2"}]}, 78 | "123": {"names": ["crowd and riot control", "crowd control", "riot control"], "cat": "capability", "icon": [{"textm1": "CRC"}]}, 79 | "124": {"names": ["explosive ordnance disposal", "EOD"], "cat": "capability", "icon": [{"textm1": "EOD"}]}, 80 | "125": {"names": ["intelligence surveillance and reconnaissance", "ISR", "ISTAR"], "cat": "capability", "icon": [{"textm1": "ISR"}], "comment": "ISTAR isn't a separate item in the current standard."}, 81 | "126": {"names": ["maintenance"], "cat": "capability", "icon": [{"d": "m 83,70 h 34 m 8,-7 c -10,0 -10,14 0,14 M 75,63 c 10,0 10,14 0,14"}]}, 82 | "127": {"names": ["medevac", "medical", "medic", "corpsman"], "cat": "capability", "icon": [{"d": "m 95.3,55 h 9.7 v 6.8 h 7 v 9.4 h -7 V 78 h -9.7 v -6.8 h -6.8 v -9.4 h 6.8 z", "stroke": false, "fill": true}]}, 83 | 84 | "128": {"names": ["search and rescue", "SAR"], "cat": "capability", "icon": [{"textm1": "SAR"}]}, 85 | "129": {"names": ["security"], "cat": "capability", "icon": [{"textm1": "SEC"}]}, 86 | "130": {"names": ["sniper"], "cat": "capability", "icon": [{"d": "m 75,62 h 20 m 5,16 V 62.1 M 125,62 h -20"}]}, 87 | 88 | "131": {"names": ["special operations forces", "SOF"], "cat": "capability", "icon": [{"textm1": "SOF"}]}, 89 | "132": {"names": ["special weapons and tactics", "SWAT"], "cat": "capability", "icon": [{"textm1": "SWAT"}]}, 90 | "133": {"names": ["guided missile"], "cat": "capability", "icon": [{"textm1": "G"}]}, 91 | "134": {"names": ["other guided missile"], "cat": "capability", "icon": [{"textm1": "GM"}]}, 92 | 93 | "135": {"names": ["petroleum oil and lubricants", "petroleum", "POL"], "cat": "capability", "icon": [{"d": "M 100,79 V 69 L 91,57 h 18 l -9,12"}]}, 94 | "136": {"names": ["water"], "cat": "capability", "icon": [{"d": "m 92,59 h 16 m -8,9.7 V 59 M 75,69 h 40 c 10,0 15,5 15,10"}]}, 95 | "137": {"names": ["weapons"], "cat": "capability", "icon": [{"textm1": "WPN"}]}, 96 | 97 | "138": {"names": ["chemical"], "cat": "CBRN", "icon": [{"textm1": "C"}]}, 98 | "140": {"names": ["radiological"], "cat": "CBRN", "icon": [{"textm1": "R"}]}, 99 | "141": {"names": ["nuclear"], "cat": "CBRN", "icon": [{"textm1": "N"}]}, 100 | "142": {"names": ["decontamination", "decon"], "cat": "CBRN", "icon": [{"textm1": "D"}]}, 101 | 102 | "143": {"names": ["civilian"], "cat": "organization", "civ": true, "icon": [{"textm1": "CIV"}]}, 103 | "144": {"names": ["government organization", "government"], "cat": "organization", "icon": [{"textm1": "GO"}]}, 104 | 105 | "145": {"names": ["accident"], "cat": "composite loss", "icon": [{"textm1": "ACC"}]}, 106 | 107 | "146": {"names": ["assassination"], "cat": "crime", "icon": [{"textm1": "AS"}]}, 108 | "147": {"names": ["execution"], "cat": "crime", "icon": [{"textm1": "EX"}]}, 109 | "148": {"names": ["kidnapping", "kidnap"], "cat": "crime", "icon": [{"textm1": "KNA"}]}, 110 | "149": {"names": ["piracy"], "cat": "crime", "icon": [{"textm1": "PI"}]}, 111 | "150": {"names": ["rape"], "cat": "crime", "icon": [{"textm1": "RA"}]}, 112 | 113 | "151": {"names": ["anti-submarine warfare", "antisubmarine warfare", "ASW"], "cat": "mission area", "icon": [{"textm1": "ASW"}]}, 114 | "152": {"names": ["escort"], "cat": "mission area", "icon": [{"textm1": "E"}]}, 115 | "153": {"names": ["mine countermeasures"], "cat": "mission area", "icon": [{"textm1": "MCM"}]}, 116 | "154": {"names": ["mine warfare"], "cat": "mission area", "icon": [{"textm1": "MIW"}]}, 117 | "155": {"names": ["surface warfare"], "cat": "mission area", "icon": [{"textm1": "SUW"}]}, 118 | 119 | "156": {"names": ["command"], "cat": "support level", "icon": [{"d": "m 110,59 v 16 m -8,-8 h 16 M 90,59 v 16 m 8,-8 H 82"}]}, 120 | "157": {"names": ["company", "battery"], "cat": "support level", "icon": [{"d": "M 100,59.6 V 78"}]}, 121 | "158": {"names": ["platoon", "detachment"], "cat": "support level", "icon": [ 122 | {"pos": [80, 68], "r": 8, "fill": true, "stroke": false}, 123 | {"pos": [100, 68], "r": 8, "fill": true, "stroke": false}, 124 | {"pos": [120, 68], "r": 8, "fill": true, "stroke": false} 125 | ]}, 126 | "157": {"names": ["regiment", "group"], "cat": "support level", "icon": [{"d": "m 110,60 v 18 0 M 90,60 v 18 m 10,-18 v 18"}]}, 127 | "160": {"names": ["section"], "cat": "support level", "icon": [ 128 | {"pos": [90, 68], "r": 8, "fill": true, "stroke": false}, 129 | {"pos": [110, 68], "r": 8, "fill": true, "stroke": false} 130 | ]}, 131 | "161": {"names": ["squad"], "cat": "support level", "icon": [{"pos": [100, 68], "r": 8, "fill": true, "stroke": false}]}, 132 | "162": {"names": ["team", "crew"], "cat": "support level", "icon": [ 133 | {"pos": [100, 65], "r": 10}, 134 | {"d": "m 90,75 l20,-20"} 135 | ]}, 136 | "163": {"names": ["battalion", "squadron"], "cat": "support level", "icon": [{"d": "m 105,60 v 18 0 M 95,60 v 18"}]}, 137 | 138 | "164": {"names": ["directed energy", "laser"], "cat": "capability", "icon": [{"d": "M 132,70.8 H 114 L 110,78 108,63.6 104,78 100,63.6 96.5,70.8 H 85.7 L 82.1,78 78.5,63.6 74.9,78 71.3,63.6 67.7,78 m 56.3,-14.4 8,7.2 -8,7.2"}]}, 139 | "150": {"names": ["hijacker", "hijack", "hijacking"], "cat": "crime", "icon": [{"textm1": "HIJ"}]} 140 | }, 141 | 142 | "M2": { 143 | "100": {"names": ["airborne", "airmobile", "air mobile"], "cat": "mobility", "icon": [{"d": "M75,140 C75,125 100,125 100,140 C100,125 125,125 125,140"}]}, 144 | "101": {"names": ["bicycle equipped", "bicycle"], "cat": "mobility", "icon": [{"r": 11, "pos": [100, 132]}]}, 145 | "102": {"names": ["railroad", "railway"], "cat": "mobility", "icon": [ 146 | {"d": "M65,125 l70,0"}, 147 | {"r": 5, "pos": [70, 130]}, {"r": 5, "pos": [80, 130]}, {"r": 5, "pos": [120, 130]}, {"r": 5, "pos": [130, 130]} 148 | ]}, 149 | "103": {"names": ["ski"], "cat": "mobility", "icon": [{"d": "m 95,145 -9,-8 m 28,0 -9,8 m -15,-24 20,20 m 0,-20 -20,20"}]}, 150 | "104": {"names": ["tracked"], "cat": "mobility", "icon": [{"d": "m 90,125 h 20 c 10,0 10,15 0,15 H 90 c -10,0 -10,-15 0,-15"}]}, 151 | "105": {"names": ["wheeled limited cross country", "wheeled"], "cat": "mobility", "icon": [ 152 | {"r": 7, "pos": [80, 130]}, {"r": 7, "pos": [120, 130]} 153 | ]}, 154 | "106": {"names": ["wheeled cross country", "cross country", "wheeled x"], "cat": "mobility", "icon": [ 155 | {"r": 7, "pos": [75, 130]}, 156 | {"r": 7, "pos": [100, 130]}, 157 | {"r": 7, "pos": [125, 130]} 158 | ]}, 159 | "107": {"names": ["fixed-wing", "fixed wing", "FW"], "cat": "mobility", "icon": [{"fill": true, "stroke": true, "d": "m 100,130 22,-9 c 11,0 11,18 0,18 l -22,-9 -22,9 c -10.9,0 -10.9,-18 0,-18 z"}]}, 160 | "108": {"names": ["rotary-wing", "rotary wing", "RW"], "cat": "mobility", "icon": [{"fill": true, "stroke": true, "d": "m 75,122 v 15 l 50,-15 v 15 z"}]}, 161 | "109": {"names": ["robotic", "robot"], "cat": "mobility", "icon": [{"fill": true, "stroke": false, "d": "M100,121.68L114.895,136.459C115.309,136.201 115.798,136.052 116.321,136.052C117.812,136.052 119.022,137.262 119.022,138.753C119.022,140.243 117.812,141.454 116.321,141.454C114.831,141.454 113.62,140.243 113.62,138.753C113.62,138.407 113.686,138.076 113.805,137.772L103.378,132.6L100.851,141.224C101.072,141.298 101.28,141.4 101.471,141.526C102.211,142.008 102.701,142.843 102.701,143.791C102.701,145.281 101.491,146.492 100,146.492C99.451,146.492 98.939,146.327 98.512,146.045C97.776,145.562 97.29,144.73 97.29,143.785C97.29,142.592 98.064,141.579 99.138,141.222L96.613,132.606L86.186,137.778C86.305,138.082 86.37,138.413 86.37,138.759C86.37,140.25 85.16,141.46 83.669,141.46C82.179,141.46 80.969,140.25 80.969,138.759C80.969,137.268 82.179,136.058 83.669,136.058C84.193,136.058 84.681,136.207 85.095,136.465L99.991,121.671L100,121.662L100,121.68Z"}]}, 162 | 163 | "110": {"names": ["autonomous", "autonomous control"], "cat": "capability", "icon": [{"textm2": "AUT"}]}, 164 | "111": {"names": ["remotely piloted"], "cat": "capability", "icon": [{"textm2": "RP"}]}, 165 | "112": {"names": ["expendable"], "cat": "capability", "icon": [{"textm2": "EXP"}]}, 166 | "113": {"names": ["mountain"], "cat": "capability", "icon": [{"d": "m 87,142 10,-20 5,10 3,-5 8,15", "fill": true, "stroke": false}]}, 167 | "114": {"names": ["long range", "long-range"], "cat": "capability", "icon": [{"textm2": "LR"}]}, 168 | "115": {"names": ["medium range", "medium-range"], "cat": "capability", "icon": [{"textm2": "MR"}]}, 169 | "116": {"names": ["short range", "short-range"], "cat": "capability", "icon": [{"textm2": "SR"}]}, 170 | "117": {"names": ["close range", "close-range"], "cat": "capability", "icon": [{"textm2": "CR"}]}, 171 | 172 | "118": {"names": ["heavy"], "cat": "capability", "icon": [{"textm2": "H"}]}, 173 | "119": {"names": ["medium"], "cat": "capability", "icon": [{"textm2": "M"}]}, 174 | "120": {"names": ["light and medium", "medium and light"], "cat": "capability", "icon": [{"textm2": "L/M"}]}, 175 | "121": {"names": ["light"], "cat": "capability", "icon": [{"textm2": "L"}]}, 176 | "122": {"names": ["cyberspace", "cyber"], "cat": "capability", "icon": [{"textm2": "CYB"}]}, 177 | "123": {"names": ["security force assistance", "SFA"], "cat": "capability", "icon": [{"textm2": "SFA"}]}, 178 | 179 | "124": {"names": ["medical bed"], "cat": "capability", "icon": [{"d": "m 107,125 8,6 m 15,-7 v 13 m -23,-16 v 16 m 0,-6 h 23"}]}, 180 | "125": {"names": ["multifunctional", "multi-function", "multi-functional", "multifunction"], "cat": "capability", "icon": [{"textm2": "MF"}]} 181 | } 182 | } -------------------------------------------------------------------------------- /src/military_symbol/schema/dismounted_individual.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "27", 3 | "name": "dismounted individual", 4 | "dimension": "dismounted individual", 5 | "modcats": ["capability", "crime", "CBRN", "command level", "mobility"], 6 | "match weight": -1, 7 | "IC": { 8 | "110201": { 9 | "names": ["explosive ordnance disposal", "EOD"], 10 | "icon": [{"text": "EOD"}] 11 | }, 12 | "110202": { 13 | "names": ["field artillery observer", "artillery observer"], 14 | "icon": [ 15 | {"r": 3, "pos": [100, 68], "fill": true, "stroke": true}, 16 | {"d": "M 100,53.1 85.8,75.9 H 114 Z"} 17 | ] 18 | }, 19 | "110203": { 20 | "names": ["joint fire support"], 21 | "icon": [{"text": "JFS"}] 22 | }, 23 | "110204": { 24 | "names": ["liaison"], 25 | "icon": [{"text": "LO"}] 26 | }, 27 | "110205": { 28 | "names": ["messenger"], 29 | "icon": [{"text": "M"}] 30 | }, 31 | "110206": { 32 | "names": ["military police", "MP"], 33 | "icon": [{"text": "MP"}] 34 | }, 35 | "110207": { 36 | "names": ["observer"], 37 | "icon": [{"d": "M 100,53.1 85.8,75.9 H 114 Z"}] 38 | }, 39 | "110208": { 40 | "names": ["security"], 41 | "icon": [{"text": "SEC"}] 42 | }, 43 | "110209": { 44 | "names": ["sniper"], 45 | "icon": [{"d": "M 60 85 L 90 85 L 60 85 z M 110 85 L 140 85 L 110 85 z M 100 90 L 100 115 L 100 90 z"}] 46 | }, 47 | "110210": { 48 | "names": ["special operations forces", "SOF"], 49 | "icon": [{"text": "SOF"}] 50 | }, 51 | "110211": { 52 | "names": ["designated marksman", "DM", "DMR"], 53 | "icon": [{"text": "DM"}] 54 | }, 55 | "110212": { 56 | "names": ["medic", "corpsman"], 57 | "icon": [{"fill": true, "stroke": false, "d": "M93,83 l14,0 0,10 10,0 0,14 -10,0 0,10 -14,0 0,-10 -10,0 0,-14 10,0 Z"}] 58 | }, 59 | "110213": { 60 | "names": ["signaler", "radioman", "radio operator"], 61 | "icon": [{"d": "M 52,66.8 100,110 l 0,-20 47.9,43.1"}] 62 | }, 63 | "110214": { 64 | "names": ["reconnaissance", "recon", "scout"], 65 | "icon": [{ 66 | "friend": [{"d": "M 45,130 155,70"}], 67 | "hostile": [{"d": "M60,130L140,70"}], 68 | "neutral": [{"d": "M45,155L155,45"}], 69 | "unknown": [{"d": "M50,135L150,65"}] 70 | }] 71 | }, 72 | "110215": { 73 | "names": ["infantry"], 74 | "icon": [{ 75 | "friend": [{"d": "m 45,70 110,60 M 45,130 155,70"}], 76 | "hostile": [{"d": "M60,70L140,130M60,130L140,70"}], 77 | "neutral": [{"d": "M45,45L155,155M45,155L155,45"}], 78 | "unknown": [{"d": "M50,65L150,135M50,135L150,65"}] 79 | }] 80 | }, 81 | "110216": { 82 | "names": ["close protection"], 83 | "icon": [{"text": "CLP"}] 84 | }, 85 | "110217": { 86 | "names": ["crowd and riot control", "crowd control", "riot control"], 87 | "icon": [{"text": "CRC"}] 88 | }, 89 | "110218": { 90 | "names": ["special weapons and tactics", "SWAT"], 91 | "icon": [{"text": "SWAT"}] 92 | }, 93 | "110219": { 94 | "names": ["demolition"], 95 | "icon": [{"text": "DEM"}] 96 | }, 97 | "110220": { 98 | "names": ["commander"], 99 | "icon": [{"text": "CDR"}] 100 | }, 101 | "110221": { 102 | "names": ["second in command"], 103 | "icon": [{"text": "SIC"}] 104 | }, 105 | 106 | "110300": { 107 | "names": ["lethal weapons"], 108 | "icon": [] 109 | }, 110 | 111 | ".light": { 112 | "names": [".light"], 113 | "icon": [{"d": "m 85,100 30,0"}] 114 | }, 115 | ".medium": { 116 | "names": [".medium"], 117 | "icon": [{"d": "m 85,105 30,0 m -30,-10 30,0"}] 118 | }, 119 | ".heavy": { 120 | "names": [".heavy"], 121 | "icon": [{"d": "m 85,110 30,0 m -30,-20 30,0 m -30,10 30,0"}] 122 | }, 123 | 124 | "110301": { 125 | "names": ["rifle"], 126 | "icon": [{"d": "m 100,60 0,80 M 85,75 100,60 115,75"}] 127 | }, 128 | "110302": { 129 | "names": ["single shot rifle"], 130 | "icon": [{"icon": "110301"}, {"icon": ".light"}] 131 | }, 132 | "110303": { 133 | "names": ["semiautomatic rifle"], 134 | "icon": [{"icon": "110301"}, {"icon": ".medium"}] 135 | }, 136 | "110304": { 137 | "names": ["automatic rifle"], 138 | "icon": [{"icon": "110301"}, {"icon": ".light"}] 139 | }, 140 | "110305": { 141 | "names": ["machine gun"], 142 | "icon": [{"d": "m 100,60 0,80 M 85,75 100,60 115,75 M 80,140 120,140"}] 143 | }, 144 | "110306": { 145 | "names": ["light machine gun"], 146 | "icon": [{"icon": "110305"}, {"icon": ".light"}] 147 | }, 148 | "110307": { 149 | "names": ["medium machine gun"], 150 | "icon": [{"icon": "110305"}, {"icon": ".medium"}] 151 | }, 152 | "110308": { 153 | "names": ["heavy machine gun"], 154 | "icon": [{"icon": "110305"}, {"icon": ".heavy"}] 155 | }, 156 | 157 | "110309": { 158 | "names": ["grenade launcher"], 159 | "icon": [ 160 | {"icon": "110301"}, 161 | {"r": 15, "pos": [100, 90]} 162 | ] 163 | }, 164 | "110310": { 165 | "names": ["light grenade launcher"], 166 | "icon": [{"icon": "110309"}, {"translate": [0, 20], "items": [ 167 | {"icon": ".light"} 168 | ]}] 169 | }, 170 | "110311": { 171 | "names": ["medium grenade launcher"], 172 | "icon": [{"icon": "110309"}, {"translate": [0, 20], "items": [ 173 | {"icon": ".medium"} 174 | ]}] 175 | }, 176 | "110312": { 177 | "names": ["heavy grenade launcher"], 178 | "icon": [{"icon": "110309"}, {"translate": [0, 20], "items": [ 179 | {"icon": ".heavy"} 180 | ]}] 181 | }, 182 | 183 | "110313": { 184 | "names": ["flame thrower", "flamethrower"], 185 | "icon": [{"d": "m 90,135 0,-70 c 0,-15 20,-15 20,0"}] 186 | }, 187 | "110314": { 188 | "names": ["mortar"], 189 | "icon": [ 190 | {"d": "m 100,60 0,60 M 85,75 100,60 115,75"}, 191 | {"r": 10, "pos": [100, 130]} 192 | ] 193 | }, 194 | "110315": { 195 | "names": ["single rocket launcher"], 196 | "icon": [{"d": "m 85,75 15,-15 15,15 m -15,-5 0,70 M 85,85 100,70 115,85"}] 197 | }, 198 | "110316": { 199 | "names": ["anti-tank rocket launcher", "antitank rocket launcher"], 200 | "icon": [{"d": "m 85,140 15,-15 15,15 M 85,85 100,70 115,85 m -15,-15 0,55 M 85,75 100,60 115,75"}] 201 | }, 202 | 203 | "110400": { 204 | "names": ["nonlethal weapons"], 205 | "match weight": -1, 206 | "icon": [] 207 | }, 208 | "110401": { 209 | "names": ["nonlethal weapon"], 210 | "icon": [{"d": "m 100,60 0,80 M 80,60 l40,0"}] 211 | }, 212 | "110402": { 213 | "names": ["nonlethal grenade launcher"], 214 | "icon": [ 215 | {"icon": "110401"}, 216 | {"r": 10, "pos": [100, 130]} 217 | ] 218 | }, 219 | "110403": { 220 | "names": ["taser"], 221 | "icon": [ 222 | {"icon": "110401"}, 223 | {"text": "Z"} 224 | ] 225 | } 226 | }, 227 | "M1": { 228 | "07": { 229 | "names": ["non-governmental organization", "non-government organizaton", "NGO"], 230 | "cat": "capability", 231 | "icon": [{"textm1": "NGO"}] 232 | }, 233 | "11": { 234 | "names": ["field artillery observer", "artillery observer"], 235 | "cat": "capability", 236 | "icon": [ 237 | {"r": 3, "fill": true, "stroke": true, "pos": [100, 68]}, 238 | {"d": "M 100,53.1 85.8,75.9 H 114 Z"} 239 | ] 240 | }, 241 | "12": { 242 | "names": ["joint fire support"], 243 | "cat": "capability", 244 | "icon": [{"textm1": "JFS"}] 245 | }, 246 | "13": { 247 | "names": ["liaison"], 248 | "cat": "capability", 249 | "icon": [{"textm1": "LO"}] 250 | }, 251 | "14": { 252 | "names": ["messenger"], 253 | "cat": "capability", 254 | "icon": [{"textm1": "MSG"}] 255 | }, 256 | "15": { 257 | "names": ["military police", "MP"], 258 | "cat": "capability", 259 | "icon": [{"textm1": "NP"}] 260 | }, 261 | "16": { 262 | "names": ["observer"], 263 | "cat": "capability", 264 | "icon": [ 265 | {"d": "M 100,53.1 85.8,75.9 H 114 Z"} 266 | ] 267 | }, 268 | "17": { 269 | "names": ["designated marksman"], 270 | "cat": "capability", 271 | "icon": [{"textm1": "DM"}] 272 | }, 273 | "20": { 274 | "names": ["signaler"], 275 | "cat": "capability", 276 | "icon": [{"textm1": "SIG"}] 277 | }, 278 | "21": { 279 | "names": ["reconnaissance", "recon", "scout"], 280 | "cat": "capability", 281 | "icon": [{"textm1": "REC"}] 282 | }, 283 | "22": { 284 | "names": ["infantry"], 285 | "cat": "capability", 286 | "icon": [{"textm1": "IN"}] 287 | }, 288 | "23": { 289 | "names": ["commander"], 290 | "cat": "capability", 291 | "icon": [{"textm1": "CDR"}] 292 | }, 293 | "24": { 294 | "names": ["second in command"], 295 | "cat": "capability", 296 | "icon": [{"textm1": "SIC"}] 297 | }, 298 | "25": { 299 | "names": ["demolition"], 300 | "cat": "capability", 301 | "icon": [{"textm1": "DEM"}] 302 | }, 303 | "26": { 304 | "names": ["police"], 305 | "cat": "capability", 306 | "icon": [{"d": "m 100,77.4 c 13,-4.9 8,-12.6 10,-19.6 -3,3.8 -7,3.8 -10,0 -3.2,3.8 -6.5,3.8 -9.7,0 1.6,7 -3.4,14.7 9.7,19.6 z"}] 307 | }, 308 | 309 | "46": { 310 | "names": ["individual"], 311 | "cat": "support level", 312 | "icon": [{"d": "M85,65 l30,0"}] 313 | }, 314 | 315 | "47": { 316 | "names": ["team", "crew"], 317 | "cat": "support level", 318 | "until": "2525E", 319 | "icon": [{"r": 10, "pos": [100, 65]}, {"d": "m 90,75 l20,-20"}] 320 | }, 321 | "48": { 322 | "names": ["squad"], 323 | "cat": "support level", 324 | "until": "2525E", 325 | "icon": [{"r": 8, "fill": true, "stroke": false, "pos": [100, 68]}] 326 | }, 327 | "49": { 328 | "names": ["section"], 329 | "cat": "support level", 330 | "until": "2525E", 331 | "icon": [ 332 | {"r": 8, "fill": true, "stroke": false, "pos": [90, 68]}, 333 | {"r": 8, "fill": true, "stroke": false, "pos": [110, 68]} 334 | ] 335 | }, 336 | "50": { 337 | "names": ["platoon", "detachment"], 338 | "cat": "support level", 339 | "until": "2525E", 340 | "icon": [ 341 | {"r": 8, "fill": true, "stroke": false, "pos": [80, 68]}, 342 | {"r": 8, "fill": true, "stroke": false, "pos": [100, 68]}, 343 | {"r": 8, "fill": true, "stroke": false, "pos": [120, 68]} 344 | ] 345 | }, 346 | "51": { 347 | "names": ["company", "battery"], 348 | "cat": "support level", 349 | "until": "2525E", 350 | "icon": [{"d": "M 100,59.6 V 78"}] 351 | }, 352 | "52": { 353 | "names": ["battalion", "squadron"], 354 | "cat": "support level", 355 | "until": "2525E", 356 | "icon": [{"d": "m 105,60 v 18 0 M 95,60 v 18"}] 357 | }, 358 | "53": { 359 | "names": ["regiment", "group"], 360 | "cat": "support level", 361 | "until": "2525E", 362 | "icon": [{"d": "m 110,60 v 18 0 M 90,60 v 18 m 10,-18 v 18"}] 363 | } 364 | }, 365 | "M2": { 366 | "03": { 367 | "names": ["combat camera", "video imagery"], 368 | "cat": "capability", 369 | "icon": [{"d": "m 120,126 h -11 m 11,10 h -14 m 4,-14 H 80 v 18 h 25 z m 10,2 v 14"}] 370 | }, 371 | "04": { 372 | "names": ["functional staff area J-1", "G-1", "S-1"], 373 | "cat": "capability", 374 | "icon": [{"textm2": "J1"}] 375 | }, 376 | "05": { 377 | "names": ["functional staff area J-2", "G-2", "S-2"], 378 | "cat": "capability", 379 | "icon": [{"textm2": "J2"}] 380 | }, 381 | "06": { 382 | "names": ["functional staff area J-3", "G-3", "S-3"], 383 | "cat": "capability", 384 | "icon": [{"textm2": "J3"}] 385 | }, 386 | "07": { 387 | "names": ["functional staff area J-4", "G-4", "S-4"], 388 | "cat": "capability", 389 | "icon": [{"textm2": "J4"}] 390 | }, 391 | "08": { 392 | "names": ["functional staff area J-5", "G-5", "S-5"], 393 | "cat": "capability", 394 | "icon": [{"textm2": "J5"}] 395 | }, 396 | "09": { 397 | "names": ["functional staff area J-6", "G-6", "S-6"], 398 | "cat": "capability", 399 | "icon": [{"textm2": "J6"}] 400 | }, 401 | "10": { 402 | "names": ["functional staff area J-7", "G-7", "S-7"], 403 | "cat": "capability", 404 | "icon": [{"textm2": "J7"}] 405 | }, 406 | "11": { 407 | "names": ["functional staff area J-8", "G-8", "S-8"], 408 | "cat": "capability", 409 | "icon": [{"textm2": "J8"}] 410 | }, 411 | "12": { 412 | "names": ["functional staff area J-9", "G-9", "S-9"], 413 | "cat": "capability", 414 | "icon": [{"textm2": "J9"}] 415 | }, 416 | 417 | 418 | "14": { 419 | "names": ["rank O-1/O-2", "O-1/O-2", "O-1", "O-2", "OF-1", "rank OF-1"], 420 | "cat": "command level", 421 | "icon": [{"textm2": "O-3"}], 422 | "alt icon": [{"textm2": "OF-2"}] 423 | }, 424 | "15": { 425 | "names": ["rank O-3", "O-3", "OF-2", "rank OF-2"], 426 | "cat": "command level", 427 | "icon": [{"textm2": "O-3"}], 428 | "alt icon": [{"textm2": "OF-2"}] 429 | }, 430 | "16": { 431 | "names": ["rank O-4", "O-4", "OF-3", "rank OF-3"], 432 | "cat": "command level", 433 | "icon": [{"textm2": "O-4"}], 434 | "alt icon": [{"textm2": "OF-3"}] 435 | }, 436 | "17": { 437 | "names": ["rank O-5", "O-5", "OF-4", "rank OF-4"], 438 | "cat": "command level", 439 | "icon": [{"textm2": "O-5"}], 440 | "alt icon": [{"textm2": "OF-4"}] 441 | }, 442 | "18": { 443 | "names": ["rank O-6", "O-6", "OF-5", "rank OF-5"], 444 | "cat": "command level", 445 | "icon": [{"textm2": "O-6"}], 446 | "alt icon": [{"textm2": "OF-5"}] 447 | }, 448 | "19": { 449 | "names": ["rank O-7", "O-7", "OF-6", "rank OF-6"], 450 | "cat": "command level", 451 | "icon": [{"textm2": "O-7"}], 452 | "alt icon": [{"textm2": "OF-6"}] 453 | }, 454 | "20": { 455 | "names": ["rank O-8", "O-8", "OF-7", "rank OF-7"], 456 | "cat": "command level", 457 | "icon": [{"textm2": "O-8"}], 458 | "alt icon": [{"textm2": "OF-7"}] 459 | }, 460 | "21": { 461 | "names": ["rank O-9", "O-9", "OF-8", "rank OF-8"], 462 | "cat": "command level", 463 | "icon": [{"textm2": "O-9"}], 464 | "alt icon": [{"textm2": "OF-8"}] 465 | }, 466 | "22": { 467 | "names": ["rank O-10", "O-10", "OF-9", "rank OF-9"], 468 | "cat": "command level", 469 | "icon": [{"textm2": "O-10"}], 470 | "alt icon": [{"textm2": "OF-9"}] 471 | }, 472 | "23": { 473 | "names": ["rank O-11", "O-11", "OF-10", "rank OF-10"], 474 | "cat": "command level", 475 | "icon": [{"textm2": "O-11"}], 476 | "alt icon": [{"textm2": "OF-10"}] 477 | }, 478 | "25": { 479 | "names": ["rank E-1", "E-1", "OR-1", "rank OR-1"], 480 | "cat": "command level", 481 | "icon": [{"textm2": "E-1"}], 482 | "alt icon": [{"textm2": "OR-1"}] 483 | }, 484 | "26": { 485 | "names": ["rank E-2", "E-2", "OR-2", "rank OR-2"], 486 | "cat": "command level", 487 | "icon": [{"textm2": "E-2"}], 488 | "alt icon": [{"textm2": "OR-2"}] 489 | }, 490 | "27": { 491 | "names": ["rank E-3", "E-3", "OR-3", "rank OR-3"], 492 | "cat": "command level", 493 | "icon": [{"textm2": "E-3"}], 494 | "alt icon": [{"textm2": "OR-3"}] 495 | }, 496 | "28": { 497 | "names": ["rank E-4", "E-4", "OR-4", "rank OR-4"], 498 | "cat": "command level", 499 | "icon": [{"textm2": "E-4"}], 500 | "alt icon": [{"textm2": "OR-4"}] 501 | }, 502 | "29": { 503 | "names": ["rank E-5", "E-5", "OR-5", "rank OR-5"], 504 | "cat": "command level", 505 | "icon": [{"textm2": "E-5"}], 506 | "alt icon": [{"textm2": "OR-5"}] 507 | }, 508 | "30": { 509 | "names": ["rank E-6", "E-6", "OR-6", "rank OR-6"], 510 | "cat": "command level", 511 | "icon": [{"textm2": "E-6"}], 512 | "alt icon": [{"textm2": "OR-6"}] 513 | }, 514 | "31": { 515 | "names": ["rank E-7", "E-7", "OR-7", "rank OR-7"], 516 | "cat": "command level", 517 | "icon": [{"textm2": "E-7"}], 518 | "alt icon": [{"textm2": "OR-7"}] 519 | }, 520 | "32": { 521 | "names": ["rank E-8", "E-8", "OR-8", "rank OR-8"], 522 | "cat": "command level", 523 | "icon": [{"textm2": "E-8"}], 524 | "alt icon": [{"textm2": "OR-8"}] 525 | }, 526 | "33": { 527 | "names": ["rank E-9", "E-9", "OR-9", "rank OR-9"], 528 | "cat": "command level", 529 | "icon": [{"textm2": "E-9"}], 530 | "alt icon": [{"textm2": "OR-9"}] 531 | }, 532 | "34": { 533 | "names": ["rank WO-1", "W-1", "rank W-1", "WO-1"], 534 | "cat": "capability", 535 | "icon": [{"textm2": "WO-1"}] 536 | }, 537 | "35": { 538 | "names": ["rank WO-2", "W-2", "rank W-2", "WO-2"], 539 | "cat": "capability", 540 | "icon": [{"textm2": "WO-2"}] 541 | }, 542 | "36": { 543 | "names": ["rank WO-3", "W-3", "rank W-3", "WO-3"], 544 | "cat": "capability", 545 | "icon": [{"textm2": "WO-3"}] 546 | }, 547 | "37": { 548 | "names": ["rank WO-4", "W-4", "rank W-4", "WO-4"], 549 | "cat": "capability", 550 | "icon": [{"textm2": "WO-4"}] 551 | }, 552 | "38": { 553 | "names": ["rank WO-5", "W-5", "rank W-5", "WO-5"], 554 | "cat": "capability", 555 | "icon": [{"textm2": "WO-5"}] 556 | } 557 | } 558 | } -------------------------------------------------------------------------------- /src/military_symbol/schema/land_civilian.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "11", 3 | "name": "land civilian unit/organization", 4 | "dimension": "land unit", 5 | "modcats": ["capability", "crime", "organization", "composite loss"], 6 | "IC": { 7 | "110000": { 8 | "names": ["civilian"], 9 | "civ": true, 10 | "icon": [{"text": "CIV", "fill": "white", "stroke": true}] 11 | }, 12 | "110100": { 13 | "names": ["environmental protection"], 14 | "civ": true, 15 | "icon": [{"fill": "white", "d": "m 100,80 -10,15 5,0 -10,10 5,0 -10,10 15,0 0,5 10,0 0,-5 15,0 -10,-10 5,0 -10,-10 5,0 z"}] 16 | }, 17 | "110200": { 18 | "names": ["governmental organization"], 19 | "civ": true, 20 | "icon": [{"text": "GO"}] 21 | }, 22 | "110300": { 23 | "names": ["individual"], 24 | "civ": true, 25 | "icon": [{"d": "m 108,90 c 0,10 -15,10 -15,0 0,-10 15,-10 15,0 z m -8,7.3 0,25 m -10,-20 20,0"}] 26 | }, 27 | "110400": { 28 | "names": ["group", "organization"], 29 | "civ": true, 30 | "icon": [{"d": "m 133,90 c 0,10 -15,10 -15,0 0,-10 15,-10 15,0 z m -8,7.3 0,25 m -10,-20 20,0 m -52,-12.3 c 0,10 -15,10 -15,0 0,-10 15,-10 15,0 z m -8,7.3 0,25 m -10,-20 20,0 m 23,-7.3 c 0,10 -15,10 -15,0 0,-10 15,-10 15,0 z m -8,7.3 0,25 m -10,-20 20,0"}] 31 | }, 32 | 33 | ".killing": { 34 | "names": [".killing"], 35 | "civ": true, 36 | "icon": [ 37 | { 38 | "unknown": [{"d": "M50,65 150,135"}], 39 | "friend": [{"d": "M25,50 175,150"}], 40 | "neutral": [{"d": "M45,45 155,155"}], 41 | "hostile": [{"d": "M57,70 143,130"}] 42 | } 43 | ] 44 | }, 45 | 46 | "110500": { 47 | "names": ["killing victim"], 48 | "civ": true, 49 | "icon": [ 50 | {"icon": "110300"}, 51 | {"icon": ".killing"} 52 | ] 53 | }, 54 | "110600": { 55 | "names": ["killing victims"], 56 | "civ": true, 57 | "icon": [ 58 | {"icon": "110400"}, 59 | {"icon": ".killing"} 60 | ] 61 | }, 62 | "110700": { 63 | "names": ["victim of an attempted crime", "crime victim"], 64 | "civ": true, 65 | "icon": [ 66 | {"icon": "110300"}, 67 | {"d": "m 127,127 5,5 m -15,-15 5,5 m -15,-15 5,5 m -15,-15 5,5 m -15,-15 5,5 m -15,-15 5,5 m -15,-15 5,5"} 68 | ] 69 | }, 70 | "110800": { 71 | "names": ["SPY"], 72 | "civ": true, 73 | "icon": [ 74 | {"text": "SPY"} 75 | ] 76 | }, 77 | "110900": { 78 | "names": ["composite loss"], 79 | "civ": true, 80 | "icon": [{"d": "m 100,85 0,30 m -35,-15 45,0 m 20,0 c 0,5.5 -4.5,10 -10,10 -5.5,0 -10,-4.5 -10,-10 0,-5.5 4.5,-10 10,-10 5.5,0 10,4.5 10,10 z"}] 81 | }, 82 | "111000": { 83 | "names": ["emergency medical operation"], 84 | "civ": true, 85 | "icon": [{"fill": true, "d": "m 90,60 0,22.7 -19.7,-11.3 -10,17.3 L 80,100 l -19.7,11.3 10,17.3 L 90,117.3 90,140 l 20,0 0,-22.7 19.7,11.3 10,-17.3 L 120,100 l 19.7,-11.3 -10,-17.3 L 110,82.7 110,60 90,60 z"}] 86 | } 87 | }, 88 | "M1": { 89 | "01": { 90 | "names": ["assassination"], 91 | "cat": "crime", 92 | "until": "2525E", 93 | "new": "C-1-146", 94 | "icon": [{"textm1": "AS"}] 95 | }, 96 | "02": { 97 | "names": ["execution (wrongful killing)", "execution"], 98 | "cat": "crime", 99 | "until": "2525E", 100 | "new": "C-1-147", 101 | "icon": [{"textm1": "EX"}] 102 | }, 103 | "03": { 104 | "names": ["murder victims"], 105 | "icon": [{"textm1": "MU"}] 106 | }, 107 | "04": { 108 | "names": ["hijacking", "hijacked"], 109 | "cat": "crime", 110 | "until": "2525E", 111 | "new": "C-1-150", 112 | "icon": [{"textm1": "H"}] 113 | }, 114 | "05": { 115 | "names": ["kidnapping"], 116 | "cat": "crime", 117 | "until": "2525E", 118 | "new": "C-1-148", 119 | "icon": [{"textm1": "K"}] 120 | }, 121 | "06": { 122 | "names": ["piracy"], 123 | "cat": "crime", 124 | "until": "2525E", 125 | "new": "C-1-149", 126 | "icon": [{"textm1": "PI"}] 127 | }, 128 | "07": { 129 | "names": ["rape"], 130 | "cat": "crime", 131 | "until": "2525E", 132 | "new": "C-1-150", 133 | "icon": [{"textm1": "RA"}] 134 | }, 135 | "08": { 136 | "names": ["civilian"], 137 | "cat": "organization", 138 | "until": "2525E", 139 | "new": "C-1-143", 140 | "icon": [{"textm1": "CIV"}] 141 | }, 142 | "09": { 143 | "names": ["displaced persons", "refugees", "evacuees"], 144 | "cat": "organization", 145 | "icon": [{"textm1": "DPRE"}] 146 | }, 147 | "10": { 148 | "names": ["foreign fighters", "foreign fighter"], 149 | "cat": "organization", 150 | "icon": [{"textm1": "FF"}] 151 | }, 152 | "11": { 153 | "names": ["gang member", "gang"], 154 | "cat": "organization", 155 | "icon": [{"textm1": "GANG"}] 156 | }, 157 | "12": { 158 | "names": ["government organization", "government"], 159 | "until": "2525E", 160 | "new": "C-1-144", 161 | "cat": "organization", 162 | "icon": [{"textm1": "GO"}] 163 | }, 164 | "13": { 165 | "names": ["leader", "leadership"], 166 | "cat": "organization", 167 | "icon": [{"textm1": "LDR"}] 168 | }, 169 | "14": { 170 | "names": ["nongovernmental organization"], 171 | "cat": "organization", 172 | "icon": [{"textm1": "NGO"}] 173 | }, 174 | "15": { 175 | "names": ["unwilling recruit", "coerced recruit"], 176 | "cat": "organization", 177 | "icon": [{"textm1": "C"}] 178 | }, 179 | "16": { 180 | "names": ["willing recruit"], 181 | "cat": "organization", 182 | "icon": [{"textm1": "WR"}] 183 | }, 184 | "17": { 185 | "names": ["religious"], 186 | "cat": "organization", 187 | "icon": [{"textm1": "REL"}] 188 | }, 189 | "18": { 190 | "names": ["targeted individual", "targeted organization"], 191 | "cat": "organization", 192 | "icon": [{"textm1": "TGT"}] 193 | }, 194 | "19": { 195 | "names": ["terrorist", "terror"], 196 | "cat": "organization", 197 | "icon": [{"textm1": "TER"}] 198 | }, 199 | "20": { 200 | "names": ["speaker"], 201 | "cat": "organization", 202 | "icon": [{"textm1": "SPK"}] 203 | }, 204 | "21": { 205 | "names": ["accident"], 206 | "cat": "composite loss", 207 | "until": "2525E", 208 | "new": "C-1-145", 209 | "icon": [{"textm1": "ACC"}] 210 | }, 211 | "22": { 212 | "names": ["combat"], 213 | "cat": "composite loss", 214 | "until": "2525E", 215 | "new": "C-1-121", 216 | "icon": [{"textm1": "CBT"}] 217 | }, 218 | "23": { 219 | "names": ["other"], 220 | "cat": "organization", 221 | "icon": [{"textm1": "OTH"}] 222 | }, 223 | "24": { 224 | "names": ["loot", "looting", "looter"], 225 | "cat": "organization", 226 | "icon": [{"textm1": "LOOT"}] 227 | }, 228 | "25": { 229 | "names": ["hijacker"], 230 | "cat": "crime", 231 | "until": "2525E", 232 | "new": "C-1-150", 233 | "icon": [{"textm1": "HJ"}] 234 | }, 235 | "26": { 236 | "names": ["cyberspace", "cyber"], 237 | "cat": "organization", 238 | "new": "C-1-115", 239 | "until": "2525E", 240 | "icon": [{"textm1": "CYB"}] 241 | } 242 | }, 243 | "M2": { 244 | "01": { 245 | "names": ["leader"], 246 | "cat": "organization", 247 | "icon": [{"textm2": "LDR"}] 248 | }, 249 | "02": { 250 | "names": ["cyberspace"], 251 | "cat": "organization", 252 | "new": "C-2-122", 253 | "until": "2525E", 254 | "icon": [{"textm2": "CYB"}] 255 | } 256 | } 257 | } -------------------------------------------------------------------------------- /src/military_symbol/schema/sea_subsurface.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "35", 3 | "name": "sea subsurface", 4 | "dimension": "sea subsurface", 5 | "modcats": ["crime", "capability", "ship propulsion"], 6 | "IC": { 7 | "110000": { 8 | "names": ["military"], 9 | "icon": [{"text": "MIL"}] 10 | }, 11 | "110100": { 12 | "names": ["submarine"], 13 | "icon": [{"fill": true, "d": "m 75,85 50,0 15,15 -15,15 -50,0 -15,-15 z"}] 14 | }, 15 | "110101": { 16 | "names": ["submarine, surfaced", "surfaced submarine"], 17 | "icon": [ 18 | {"fill": true, "d": "m 75,80 50,0 15,15 -15,15 -50,0 -15,-15 z"}, 19 | {"d": "m 65,120 10,-10 10,10 10,-10 10,10 10,-10 10,10 10,-10"} 20 | ] 21 | }, 22 | "110102": { 23 | "names": ["submarine, snorkeling", "snorkeling submarine"], 24 | "icon": [ 25 | {"fill": true, "d": "m 75,120 -10,-10 10,-10 20,0 0,-20 10,0 0,20 20,0 10,10 -10,10 z"}, 26 | {"d": "m 65,95 10,-10 10,10 10,-10 10,10 10,-10 10,10 10,-10"} 27 | ] 28 | }, 29 | "110103": { 30 | "names": ["submarine, bottomed", "bottomed submarine"], 31 | "icon": [ 32 | {"fill": true, "d": "m 75,80 50,0 15,15 -15,15 -50,0 -15,-15 z"}, 33 | {"fill": true, "d": "m 70,120 0,-5 60,0 0,5 z"} 34 | ] 35 | }, 36 | "110200": { 37 | "names": ["other submersible"], 38 | "icon": [{"fill": true, "d": "m 85,90 0,-10 30,0 0,10 m 20,10 c 0,5.5 -15.7,10 -35,10 -19.3,0 -35,-4.5 -35,-10 0,-5.5 15.7,-10 35,-10 19.3,0 35,4.5 35,10 z"}] 39 | }, 40 | "110300": { 41 | "names": ["non-submarine", "nonsubmarine"], 42 | "icon": [ 43 | {"text": "NON", "pos": [100, 100], "fontsize": 25}, 44 | {"text": "SUB", "pos": [100, 120], "fontsize": 25} 45 | ] 46 | }, 47 | "110400": { 48 | "names": ["autonomous underwater vehicle", "unmanned underwater vehicle", "UUV", "AUV"], 49 | "icon": [{"fill": true, "d": "m 60,84 40,20 40,-20 0,8 -40,25 -40,-25 z"}] 50 | }, 51 | "110500": { 52 | "names": ["diver"], 53 | "icon": [{"fill": true, "stroke": false, "d": "M 100 80 C 93.7 80 88.3 82.7 85.8 88.3 L 85.8 88.3 L 77.8 88.3 L 77.8 105 L 85.8 105 L 85.8 104.8 C 87.3 108.2 88.8 110 92 111.7 L 92.1 111.7 L 84.2 120 L 115.8 120 L 107.9 111.7 L 108 111.7 C 111.1 110 112.8 108.3 114.3 105 L 122.2 105 L 122.2 88.3 L 114.3 88.3 L 114.3 88.3 C 111.7 82.8 106.3 80 100 80 z M 100 86.6 C 105.4 86.6 109.8 91.1 109.8 96.6 C 109.8 102.1 105.4 106.6 100 106.6 C 94.6 106.6 90.1 102.1 90.1 96.6 C 90.1 91.1 94.6 86.6 100 86.6 z M 100 89.6 C 96.2 89.6 93.1 92.7 93.1 96.6 C 93.1 100.5 96.2 103.6 100 103.6 C 103.8 103.6 106.8 100.5 106.8 96.6 C 106.8 92.7 103.8 89.6 100 89.6 z"}] 54 | }, 55 | 56 | 57 | 58 | "120000": { 59 | "names": ["civilian"], 60 | "civ": true, 61 | "icon": [{"fill": "white", "stroke": true, "text": "CIV"}] 62 | }, 63 | "120100": { 64 | "names": ["submersible"], 65 | "civ": true, 66 | "icon": [{"fill": "white", "d": "m 85,90 0,-10 30,0 0,10 m 20,10 c 0,5.5 -15.7,10 -35,10 -19.3,0 -35,-4.5 -35,-10 0,-5.5 15.7,-10 35,-10 19.3,0 35,4.5 35,10 z"}] 67 | }, 68 | "120200": { 69 | "names": [ 70 | "civilian autonomous underwater vehicle", "civilian unmanned underwater vehicle", "civilian UUV", "civilian AUV", 71 | "autonomous underwater vehicle", "unmanned underwater vehicle", "UUV", "AUV"], 72 | "civ": true, 73 | "icon": [{"fill": "white", "d": "m 60,84 40,20 40,-20 0,8 -40,25 -40,-25 z"}] 74 | }, 75 | "120300": { 76 | "names": ["civilian diver", "diver"], 77 | "civ": true, 78 | "icon": [ 79 | {"fill": "white", "d": "M 114.3,94 C 114.3,102.3 107.9,109 100,109 c -7.9,0 -14.2,-6.7 -14.2,-15 0,-8.3 6.4,-15 14.2,-15 7.9,0 14.3,6.7 14.3,15 z m 0,27 14.3,15 -57,0 14.3,-15 M 125.7,79 l 14.3,0 0,30 -14.3,0 m -51.3,0 -14.3,0 0,-30 14.3,0 m 54.2,15 c 0,16.6 -12.8,30 -28.5,30 -15.7,0 -28.5,-13.4 -28.5,-30 C 71.5,77.4 84.3,64 100,64 115.7,64 128.5,77.4 128.5,94 z"} 80 | ] 81 | }, 82 | 83 | 84 | 85 | "130000": { 86 | "names": ["weapon"], 87 | "icon": [{"text": "WPN"}] 88 | }, 89 | "130100": { 90 | "names": ["torpedo"], 91 | "icon": [{"fill": true, "stroke": false, "d": "m 65,105 -5,-5 5,-5 60,0 c 0,0 5,5 5,5 l 5,-5 0,10 -5,-5 -5,5 z"}] 92 | }, 93 | "130200": { 94 | "names": ["improvised explosive device", "IED"], 95 | "icon": [{"text": "IED"}] 96 | }, 97 | "130300": { 98 | "names": ["decoy"], 99 | "icon": [{"fill": true, "stroke": false, "d": "M 85 81 L 65 98 L 85 119 L 85 81 z M 110 81 L 90 98 L 110 119 L 110 81 z M 135 81 L 115 98 L 135 119 L 135 81 z"}] 100 | }, 101 | "140000": { 102 | "names": ["echo tracker classifier", "ETC", "possible contact", "POSCON"], 103 | "icon": [{"text": "?", "pos": [100, 130], "fontsize": 60}], 104 | "force_pending": true 105 | }, 106 | "150000": { 107 | "names": ["fused track"], 108 | "force_pending": true, 109 | "icon": [ 110 | {"d": "m 70,65 10,35 -10,35 60,0 -10,-35 10,-35"}, 111 | {"text": "?"} 112 | ] 113 | }, 114 | "160000": { 115 | "names": ["manual track"], 116 | "icon": [{"text": "MAN"}] 117 | } 118 | }, 119 | "M1": { 120 | "01": { 121 | "names": ["antisubmarine warfare", "ASW"], 122 | "cat": "mission area", 123 | "until": "2525E", 124 | "new": "C-1-151", 125 | "icon": [{"textm1": "ASW"}] 126 | }, 127 | "02": { 128 | "names": ["auxiliary"], 129 | "cat": "mission area", 130 | "icon": [{"textm1": "AUX"}] 131 | }, 132 | "03": { 133 | "names": ["command and control"], 134 | "cat": "mission area", 135 | "until": "2525E", 136 | "new": "C-1-122", 137 | "icon": [{"textm1": "C2"}] 138 | }, 139 | "04": { 140 | "names": ["intelligence, surveillance, and reconnaissance"], 141 | "cat": "mission area", 142 | "until": "2525E", 143 | "new": "C-1-125", 144 | "icon": [{"textm1": "ISR"}] 145 | }, 146 | "05": { 147 | "names": ["mine countermeasures"], 148 | "cat": "mission area", 149 | "until": "2525E", 150 | "new": "C-1-153", 151 | "icon": [{"textm1": "MCM"}] 152 | }, 153 | "06": { 154 | "names": ["mine warfare"], 155 | "cat": "mission area", 156 | "until": "2525E", 157 | "new": "C-1-154", 158 | "icon": [{"textm1": "MIW"}] 159 | }, 160 | "07": { 161 | "names": ["surface warfare"], 162 | "cat": "mission area", 163 | "until": "2525E", 164 | "new": "C-1-155", 165 | "icon": [{"textm1": "SUW"}] 166 | }, 167 | "08": { 168 | "names": ["attack"], 169 | "cat": "mission area", 170 | "until": "2525E", 171 | "new": "C-1-106", 172 | "icon": [{"textm1": "A"}] 173 | }, 174 | "09": { 175 | "names": ["ballistic missile"], 176 | "cat": "capability", 177 | "until": "2525E", 178 | "new": "C-1-108", 179 | "icon": [{"textm1": "B"}] 180 | }, 181 | "10": { 182 | "names": ["guided missile"], 183 | "cat": "capability", 184 | "until": "2525E", 185 | "new": "C-1-133", 186 | "icon": [{"textm1": "G"}] 187 | }, 188 | "11": { 189 | "names": ["other guided missile"], 190 | "cat": "capability", 191 | "until": "2525E", 192 | "new": "C-1-134", 193 | "icon": [{"textm1": "M"}] 194 | }, 195 | "12": { 196 | "names": ["special operations forces", "SOF"], 197 | "cat": "capability", 198 | "until": "2525E", 199 | "new": "C-1-131", 200 | "icon": [{"textm1": "SOF"}] 201 | }, 202 | "13": { 203 | "names": ["possible submarine low 1"], 204 | "cat": "submarine confidence", 205 | "icon": [{"textm1": "P1"}] 206 | }, 207 | "14": { 208 | "names": ["possible submarine low 2"], 209 | "cat": "submarine confidence", 210 | "icon": [{"textm1": "P2"}] 211 | }, 212 | "15": { 213 | "names": ["possible submarine high 3"], 214 | "cat": "submarine confidence", 215 | "icon": [{"textm1": "P3"}] 216 | }, 217 | "16": { 218 | "names": ["possible submarine high 4"], 219 | "cat": "submarine confidence", 220 | "icon": [{"textm1": "P4"}] 221 | }, 222 | "17": { 223 | "names": ["probable submarine"], 224 | "cat": "submarine confidence", 225 | "icon": [{"textm1": "PB"}] 226 | }, 227 | "18": { 228 | "names": ["certain submarine"], 229 | "cat": "submarine confidence", 230 | "icon": [{"textm1": "CT"}] 231 | }, 232 | "19": { 233 | "names": ["anti-torpedo torpedo"], 234 | "cat": "capability", 235 | "icon": [{"textm1": "ATT"}] 236 | }, 237 | "20": { 238 | "names": ["hijacking", "hijacked"], 239 | "cat": "crime", 240 | "until": "2525E", 241 | "new": "C-1-150", 242 | "icon": [{"textm1": "H"}] 243 | }, 244 | "21": { 245 | "names": ["hijacker"], 246 | "cat": "capability", 247 | "until": "2525E", 248 | "new": "C-1-150", 249 | "icon": [{"textm1": "HJ"}] 250 | }, 251 | "22": { 252 | "names": ["cyberspace", "cyber"], 253 | "cat": "capability", 254 | "until": "2525E", 255 | "new": "C-1-115", 256 | "icon": [{"textm1": "CYB"}] 257 | } 258 | }, 259 | "M2": { 260 | "01": { 261 | "names": ["air independent propulsion"], 262 | "cat": "ship propulsion", 263 | "icon": [{"textm2": "AI"}] 264 | }, 265 | "02": { 266 | "names": ["diesel electric, general"], 267 | "cat": "ship propulsion", 268 | "icon": [{"textm2": "D"}] 269 | }, 270 | "03": { 271 | "names": ["diesel type 1"], 272 | "cat": "ship propulsion", 273 | "icon": [{"textm2": "D1"}] 274 | }, 275 | "04": { 276 | "names": ["diesel type 2"], 277 | "cat": "ship propulsion", 278 | "icon": [{"textm2": "D2"}] 279 | }, 280 | "05": { 281 | "names": ["diesel type 3"], 282 | "cat": "ship propulsion", 283 | "icon": [{"textm2": "D3"}] 284 | }, 285 | "06": { 286 | "names": ["nuclear powered, general", "nuclear"], 287 | "cat": "ship propulsion", 288 | "icon": [{"textm2": "N"}] 289 | }, 290 | "07": { 291 | "names": ["nuclear type 1"], 292 | "cat": "ship propulsion", 293 | "icon": [{"textm2": "N1"}] 294 | }, 295 | "08": { 296 | "names": ["nuclear type 2"], 297 | "cat": "ship propulsion", 298 | "icon": [{"textm2": "N2"}] 299 | }, 300 | "09": { 301 | "names": ["nuclear type 3"], 302 | "cat": "ship propulsion", 303 | "icon": [{"textm2": "N3"}] 304 | }, 305 | "10": { 306 | "names": ["nuclear type 4"], 307 | "cat": "ship propulsion", 308 | "icon": [{"textm2": "N4"}] 309 | }, 310 | "11": { 311 | "names": ["nuclear type 5"], 312 | "cat": "ship propulsion", 313 | "icon": [{"textm2": "N5"}] 314 | }, 315 | "12": { 316 | "names": ["nuclear type 6"], 317 | "cat": "ship propulsion", 318 | "icon": [{"textm2": "N6"}] 319 | }, 320 | "13": { 321 | "names": ["nuclear type 7"], 322 | "cat": "ship propulsion", 323 | "icon": [{"textm2": "N7"}] 324 | }, 325 | "14": { 326 | "names": ["autonomous control"], 327 | "cat": "capability", 328 | "until": "2525E", 329 | "new": "C-2-110", 330 | "icon": [{"textm2": "AUT"}] 331 | }, 332 | "15": { 333 | "names": ["remotely-piloted"], 334 | "cat": "capability", 335 | "until": "2525E", 336 | "new": "C-2-111", 337 | "icon": [{"textm2": "RP"}] 338 | }, 339 | "16": { 340 | "names": ["expendable"], 341 | "cat": "capability", 342 | "until": "2525E", 343 | "new": "C-2-112", 344 | "icon": [{"textm2": "EXP"}] 345 | }, 346 | "17": { 347 | "names": ["cyberspace", "cyber"], 348 | "cat": "capability", 349 | "until": "2525E", 350 | "new": "C-2-122", 351 | "icon": [{"textm2": "CYB"}] 352 | } 353 | } 354 | } -------------------------------------------------------------------------------- /src/military_symbol/schema/sea_surface.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "30", 3 | "name": "sea surface", 4 | "dimension": "sea surface", 5 | "modcats": ["capability", "mobility", "cargo capacity", "ship propulsion", "crime"], 6 | "IC": { 7 | "110000": { 8 | "names": ["military"], 9 | "icon": [{"text": "MIL"}] 10 | }, 11 | "120000": { 12 | "names": ["military combatant"], 13 | "icon": [ 14 | {"fill": false, "stroke": true, "d": "m 86.9,110 c -3.6,2 -7.2,3.9 -10.8,5.9 2.1,2.9 6.7,3.9 10,2.1 2.6,-0.9 4.7,-3.8 3.1,-6.1 -0.8,-0.6 -1.5,-1.3 -2.3,-1.9 z m 26.3,0.1 c 3.6,2 7.2,3.9 10.8,5.9 -2.1,2.9 -6.7,3.9 -10,2.1 -2.6,-0.9 -4.7,-3.8 -3.1,-6.1 0.8,-0.6 1.5,-1.3 2.3,-1.9 z"}, 15 | {"fill": "white", "stroke": true, "strokewidth": 2, "d": "m 112.9,110 c -5.6,-4 -11.3,-7.9 -16.1,-12.5 -4.2,-4.5 -7,-9.8 -9.2,-15.1 -0.8,4.4 -0.9,9.3 2.4,13.2 3.6,4.5 8.6,8.1 13.5,11.8 2.3,1.7 4.7,3.3 7.1,4.8 0.8,-0.7 1.5,-1.5 2.3,-2.2 m -25.7,0 c 5.6,-4 11.3,-7.9 16.1,-12.5 4.2,-4.5 7,-9.8 9.2,-15.1 0.8,4.4 0.9,9.3 -2.4,13.2 -3.6,4.5 -8.6,8.1 -13.5,11.8 -2.3,1.7 -4.7,3.3 -7.1,4.8 -0.8,-0.7 -1.5,-1.5 -2.3,-2.2"} 16 | ] 17 | }, 18 | "120100": { 19 | "names": ["carrier", "aircraft carrier"], 20 | "icon": [{"fill": true, "d": "m 80,100 20,20 20,-20 -20,0 0,-20 -20,0 z"}] 21 | }, 22 | "120200": { 23 | "names": ["surface combatant, line", "line surface combatant", "line combatant"], 24 | "icon": [{"fill": true, "stroke": false, "d": "m 100,120 -25,-17 15,2 0,-10 5,0 0,-5 -15,0 0,-5 15,0 0,-5 10,0 0,5 15,0 0,5 -15,0 0,5 5,0 0,10 15,-2 z"}] 25 | }, 26 | "120201": { 27 | "names": ["battleship"], 28 | "icon": [{"text": "BB"}] 29 | }, 30 | "120202": { 31 | "names": ["cruiser"], 32 | "icon": [{"text": "CG"}] 33 | }, 34 | "120203": { 35 | "names": ["destroyer"], 36 | "icon": [{"text": "DD"}] 37 | }, 38 | "121204": { 39 | "names": ["frigate"], 40 | "icon": [{"text": "FF"}] 41 | }, 42 | "120205": { 43 | "names": ["corvette"], 44 | "icon": [{"text": "FS"}] 45 | }, 46 | "120206": { 47 | "names": ["littoral combat ship", "littoral combatant ship"], 48 | "icon": [{"text": "LCS"}] 49 | }, 50 | 51 | "120300": { 52 | "names": ["amphibious warfare ship", "amphib", "amphibious ship"], 53 | "icon": [{"fill": true, "d": "m 100,120 20,0 m -20,0 -20,-20 10,0 0,-20 20,0 0,20 10,0 z"}] 54 | }, 55 | "120301": { 56 | "names": ["amphibious command ship", "LCC"], 57 | "icon": [{"text": "LCC"}] 58 | }, 59 | "120302": { 60 | "names": ["amphibious assault ship (unspecified)"], 61 | "icon": [{"text": "LA"}] 62 | }, 63 | "120303": { 64 | "names": ["amphibious assault ship", "amphibious assault ship (general)", "LHA"], 65 | "icon": [{"text": "LHA"}] 66 | }, 67 | "120304": { 68 | "names": ["amphibious assault ship (multipurpose)", "LHD"], 69 | "icon": [{"text": "LHD"}] 70 | }, 71 | "120305": { 72 | "names": ["amphibious assault ship, helicopter", "LPH"], 73 | "icon": [{"text": "LPH"}] 74 | }, 75 | "120306": { 76 | "names": ["amphibious transport dock", "LHD"], 77 | "icon": [{"text": "LPD"}] 78 | }, 79 | "120307": { 80 | "names": ["landing ship"], 81 | "icon": [{"text": "LS"}] 82 | }, 83 | "120308": { 84 | "names": ["landing craft"], 85 | "icon": [{"text": "LC"}] 86 | }, 87 | "120400": { 88 | "names": ["mine warfare ship"], 89 | "icon": [{"fill": true, "d": "m 98.3,81 0,4.1 c -2.4,0.3 -4.6,1.4 -6.4,2.9 l -3.5,-3.5 -2.4,2.4 3.6,3.6 c -0.9,1.3 -1.5,4.9 -1.8,6.5 l -10.8,0 0,3 3,0 20,20 20,-20 3,0 0,-3 -10,0 c -1,-1.7 -2,-5.3 -3,-6.7 l 4,-3.7 -2,-2.4 -4,3.6 c -2,-1.4 -4,-2.4 -6,-2.7 l 0,-4.1 z"}] 90 | }, 91 | "120401": { 92 | "names": ["mine layer", "minelayer"], 93 | "icon": [{"text": "ML"}] 94 | }, 95 | "120402": { 96 | "names": ["mine sweeper", "minesweeper"], 97 | "icon": [{"text": "MS"}] 98 | }, 99 | "120403": { 100 | "names": ["mine sweeper, drone", "drone mine sweeper", "drone minesweeper"], 101 | "icon": [{"text": "MSD"}] 102 | }, 103 | "120404": { 104 | "names": ["mine hunter"], 105 | "icon": [{"text": "MH"}] 106 | }, 107 | "120405": { 108 | "names": ["mine countermeasures"], 109 | "icon": [{"text": "MCM"}] 110 | }, 111 | "120406": { 112 | "names": ["mine countermeasures support ship"], 113 | "icon": [{"text": "MA"}] 114 | }, 115 | 116 | 117 | "120500": { 118 | "names": ["patrol boat"], 119 | "icon": [{"fill": true, "d": "m 80,100 20,20 20,-20 -10,0 0,-20 -20,0 0,20 z"}] 120 | }, 121 | "120501": { 122 | "names": ["patrol craft, submarine chaser", "escort"], 123 | "icon": [{"text": "PC"}] 124 | }, 125 | "120502": { 126 | "names": ["patrol ship, general"], 127 | "icon": [{"text": "PG"}] 128 | }, 129 | "120600": { 130 | "names": ["decoy"], 131 | "icon": [{"fill": true, "stroke": false, "d": "M 105,110 90,95 105,80 z M 85,110 70,95 85,80 z m 40,-30 -15,15 15,15 z m -55,40 0,-5 55,0 0,5 z"}] 132 | }, 133 | "120700": { 134 | "names": ["unmanned surface vehicle", "USV"], 135 | "icon": [{"fill": true, "stroke": false, "d": "m 60,84 40,20 40,-20 0,8 -40,25 -40,-25 z"}] 136 | }, 137 | "120800": { 138 | "names": ["speedboat"], 139 | "icon": [{"fill": true, "stroke": false, "d": "m 120,120 -40,0 -15,-25 15,0 5,-15 10,0 -5,15 45,0 z"}] 140 | }, 141 | "120801": { 142 | "names": ["rigid-hull inflatable boat", "RHIB"], 143 | "icon": [ 144 | {"fill": true, "stroke": false, "d": "M 85 80 L 80 95 L 65 95 L 80 120 L 120 120 L 135 95 L 90 95 L 95 80 L 85 80 z M 87 100.7 L 93.1 100.7 C 94.6 100.7 95.7 100.8 96.4 101.1 C 97.1 101.3 97.7 101.8 98.1 102.4 C 98.5 103.1 98.7 103.8 98.7 104.7 C 98.7 105.8 98.4 106.7 97.8 107.4 C 97.1 108.1 96.2 108.5 94.9 108.7 C 95.6 109.1 96.1 109.4 96.5 109.9 C 96.9 110.3 97.4 111.1 98.1 112.2 L 99.9 115 L 96.4 115 L 94.3 111.9 C 93.6 110.8 93.1 110.1 92.8 109.8 C 92.5 109.5 92.2 109.3 91.9 109.2 C 91.6 109.1 91.2 109 90.5 109 L 89.9 109 L 89.9 115 L 87 115 L 87 100.7 z M 101.5 100.7 L 107.2 100.7 C 108.3 100.7 109.2 100.7 109.7 100.8 C 110.3 100.9 110.8 101.1 111.2 101.4 C 111.7 101.7 112 102.1 112.3 102.6 C 112.6 103.1 112.7 103.7 112.8 104.3 C 112.7 105 112.6 105.6 112.2 106.1 C 111.9 106.7 111.4 107.1 110.8 107.4 C 111.6 107.7 112.3 108.1 112.8 108.7 C 113.2 109.3 113.5 110 113.5 110.8 C 113.5 111.5 113.3 112.1 113 112.8 C 112.7 113.4 112.3 113.8 111.8 114.2 C 111.2 114.6 110.6 114.8 109.8 114.9 C 109.3 115 108.2 115 106.3 115 L 101.5 115 L 101.5 100.7 z M 104.3 103.1 L 104.3 106.4 L 106.3 106.4 C 107.4 106.4 108.1 106.3 108.3 106.3 C 108.8 106.3 109.2 106.1 109.5 105.8 C 109.8 105.5 109.9 105.1 109.9 104.7 C 109.9 104.2 109.8 103.9 109.6 103.6 C 109.3 103.3 109 103.2 108.5 103.1 C 108.2 103.1 107.4 103.1 106 103.1 L 104.3 103.1 z M 89.9 103.1 L 89.9 106.8 L 92 106.8 C 93.4 106.8 94.3 106.7 94.6 106.6 C 95 106.4 95.2 106.3 95.4 106 C 95.6 105.7 95.7 105.3 95.8 104.9 C 95.7 104.4 95.6 104 95.3 103.7 C 95.1 103.4 94.7 103.2 94.3 103.2 C 94 103.1 93.3 103.1 92.2 103.1 L 89.9 103.1 z M 104.3 108.8 L 104.3 112.6 L 107 112.6 C 108.1 112.6 108.7 112.6 109 112.5 C 109.4 112.4 109.8 112.2 110.1 111.9 C 110.3 111.6 110.5 111.2 110.5 110.7 C 110.5 110.3 110.4 109.9 110.2 109.6 C 109.9 109.3 109.6 109.1 109.3 109 C 108.9 108.8 108 108.8 106.7 108.8 L 104.3 108.8 z"}, 145 | {"fill": "icon_fill", "stroke": false, "text": "RB", "fontsize": 20, "pos": [100, 115]} 146 | ] 147 | }, 148 | "120900": { 149 | "names": ["jet ski", "jetski"], 150 | "icon": [{"fill": true, "d": "m 135,105 0,15 -60,0 -10,-15 20,-25 10,0 0,10 -5,0 -5,15 z"}] 151 | }, 152 | ".task_org": { 153 | "names": ["task org"], 154 | "icon": [{"fill": false, "stroke": true, "d": "m 110,80 15,15 0,25 M 90,80 75,95 l 0,25"}] 155 | }, 156 | "121000": { 157 | "names": ["navy task organization", "naval task organization", "naval task org"], 158 | "icon": [ 159 | {"fill": false, "stroke": true, "d": "m 110,80 15,15 0,25 M 90,80 75,95 l 0,25"}, 160 | {"fill": true, "d": "m 100,80 -15,15 0,25 30,0 0,-25 -15,-15"} 161 | ] 162 | }, 163 | "121001": { 164 | "names": ["navy task element", "naval task element"], 165 | "icon": [ 166 | {"icon": ".task_org"}, 167 | {"text": "TE", "pos": [100, 150], "fontsize": 30} 168 | ] 169 | }, 170 | "121002": { 171 | "names": ["navy task force", "naval task force"], 172 | "icon": [ 173 | {"icon": ".task_org"}, 174 | {"text": "TF", "pos": [100, 150], "fontsize": 30} 175 | ] 176 | }, 177 | "121003": { 178 | "names": ["navy task group", "naval task group"], 179 | "icon": [ 180 | {"icon": ".task_org"}, 181 | {"text": "TG", "pos": [100, 150], "fontsize": 30} 182 | ] 183 | }, 184 | "121004": { 185 | "names": ["navy task unit", "naval task unit"], 186 | "icon": [ 187 | {"icon": ".task_org"}, 188 | {"text": "TU", "pos": [100, 150], "fontsize": 30} 189 | ] 190 | }, 191 | "121005": { 192 | "names": ["convoy"], 193 | "icon": [{"fill": true, "d": "m 80,115 -20,0 0,-35 80,0 0,35 -20,0 0,-20 -40,0 z"}] 194 | }, 195 | "121100": { 196 | "names": ["sea-based X-band radar", "SBX"], 197 | "icon": [{"d": "M72,95 l30,-25 0,25 30,-25 M70,70 c0,35 15,50 50,50"}] 198 | }, 199 | 200 | 201 | 202 | "130000": { 203 | "names": ["military non-combatant"], 204 | "icon": [{"fill": true, "d": "m 80,100 0,-20 40,0 0,20 15,0 0,20 -70,0 0,-20 z"}] 205 | }, 206 | "130100": { 207 | "names": ["auxiliary ship"], 208 | "icon": [{"text": "AA"}] 209 | }, 210 | "130101": { 211 | "names": ["ammunition ship"], 212 | "icon": [{"text": "AE"}] 213 | }, 214 | "130102": { 215 | "names": ["naval stores ship"], 216 | "icon": [{"text": "AF"}] 217 | }, 218 | "130103": { 219 | "names": ["auxiliary flag ship"], 220 | "icon": [{"text": "AGF"}] 221 | }, 222 | "130104": { 223 | "names": ["intelligence collector"], 224 | "icon": [{"text": "AGI"}] 225 | }, 226 | "130105": { 227 | "names": ["oceanographic research ship"], 228 | "icon": [{"text": "AGO"}] 229 | }, 230 | "130106": { 231 | "names": ["survey ship"], 232 | "icon": [{"text": "AGS"}] 233 | }, 234 | 235 | ".white_ship": { 236 | "names": [".white_ship"], 237 | "icon": [{"fill": "white", "d": "m 75,100 0,-35 50,0 0,35 20,0 -15,35 -60,0 -15,-35 z"}] 238 | }, 239 | 240 | "130107": { 241 | "names": ["hospital ship"], 242 | "icon": [{"text": "AH"}] 243 | }, 244 | "130108": { 245 | "names": ["naval cargo ship"], 246 | "icon": [{"text": "AK"}] 247 | }, 248 | "130109": { 249 | "names": ["fast combat support ship", "combat support ship, fast"], 250 | "icon": [{"text": "AOE"}] 251 | }, 252 | "130110": { 253 | "names": ["oiler, replenishment", "replenishment oiler"], 254 | "icon": [{"text": "AO"}] 255 | }, 256 | "130111": { 257 | "names": ["repair ship"], 258 | "icon": [{"text": "AR"}] 259 | }, 260 | "130112": { 261 | "names": ["submarine tender"], 262 | "icon": [{"text": "AS"}] 263 | }, 264 | "130113": { 265 | "names": ["ocean-going tug", "tug, ocean-going"], 266 | "icon": [{"text": "AT"}] 267 | }, 268 | "130200": { 269 | "names": ["service craft", "service yard"], 270 | "icon": [{"text": "YY"}] 271 | }, 272 | "130201": { 273 | "names": ["barge, not self-propelled"], 274 | "icon": [ 275 | {"text": "YB"} 276 | ] 277 | }, 278 | "130202": { 279 | "names": ["barge, self-propelled"], 280 | "icon": [{"text": "YS"}] 281 | }, 282 | "130203": { 283 | "names": ["tug, harbor", "harbor tug"], 284 | "icon": [{"text": "YT"}] 285 | }, 286 | "130204": { 287 | "names": ["launch"], 288 | "icon": [{"text": "YFT"}] 289 | }, 290 | 291 | 292 | "140000": { 293 | "names": ["civilian"], 294 | "civ": true, 295 | "icon": [{"text": "CIV", "fill": "white", "stroke": true}] 296 | }, 297 | "140100": { 298 | "names": ["merchant ship"], 299 | "civ": true, 300 | "icon": [{"icon": ".white_ship"}] 301 | }, 302 | "140101": { 303 | "names": ["cargo ship"], 304 | "civ": true, 305 | "icon": [ 306 | {"icon": ".white_ship"}, 307 | {"text": "CA", "pos": [100, 115], "fontsize": 30} 308 | ] 309 | }, 310 | "140102": { 311 | "names": ["container ship"], 312 | "civ": true, 313 | "icon": [ 314 | {"icon": ".white_ship"}, 315 | {"text": "C", "pos": [100, 115], "fontsize": 30} 316 | ] 317 | }, 318 | "140103": { 319 | "names": ["dredge"], 320 | "civ": true, 321 | "icon": [ 322 | {"icon": ".white_ship"}, 323 | {"text": "D", "pos": [100, 115], "fontsize": 30} 324 | ] 325 | }, 326 | "140104": { 327 | "names": ["roll on/roll off", "RORO"], 328 | "civ": true, 329 | "icon": [ 330 | {"icon": ".white_ship"}, 331 | {"text": "RO", "pos": [100, 115], "fontsize": 30} 332 | ] 333 | }, 334 | "140105": { 335 | "names": ["ferry"], 336 | "civ": true, 337 | "icon": [ 338 | {"icon": ".white_ship"}, 339 | {"text": "FE", "pos": [100, 115], "fontsize": 30} 340 | ] 341 | }, 342 | "140106": { 343 | "names": ["heavy lift"], 344 | "civ": true, 345 | "icon": [ 346 | {"icon": ".white_ship"}, 347 | {"text": "H", "pos": [100, 115], "fontsize": 30} 348 | ] 349 | }, 350 | "140107": { 351 | "names": ["hovercraft"], 352 | "civ": true, 353 | "icon": [ 354 | {"icon": ".white_ship"}, 355 | {"text": "J", "pos": [100, 115], "fontsize": 30} 356 | ] 357 | }, 358 | "140108": { 359 | "names": ["lash carrier with barges", "lash carrier"], 360 | "civ": true, 361 | "icon": [ 362 | {"icon": ".white_ship"}, 363 | {"text": "L", "pos": [100, 115], "fontsize": 30} 364 | ] 365 | }, 366 | "140109": { 367 | "names": ["oiler", "tanker", "oiler/tanker"], 368 | "civ": true, 369 | "icon": [ 370 | {"icon": ".white_ship"}, 371 | {"text": "OT", "pos": [100, 115], "fontsize": 30} 372 | ] 373 | }, 374 | "140110": { 375 | "names": ["passenger"], 376 | "civ": true, 377 | "icon": [ 378 | {"icon": ".white_ship"}, 379 | {"text": "PA", "pos": [100, 115], "fontsize": 30} 380 | ] 381 | }, 382 | "140111": { 383 | "names": ["tug, ocean-going", "ocean-going tug"], 384 | "match weight": -1, 385 | "civ": true, 386 | "icon": [ 387 | {"icon": ".white_ship"}, 388 | {"text": "TU", "pos": [100, 115], "fontsize": 30} 389 | ] 390 | }, 391 | "140112": { 392 | "names": ["tow"], 393 | "civ": true, 394 | "icon": [ 395 | {"icon": ".white_ship"}, 396 | {"text": "TW", "pos": [100, 115], "fontsize": 30} 397 | ] 398 | }, 399 | "140113": { 400 | "names": ["transport ship, hazardous material", "HAZMAT transport ship"], 401 | "civ": true, 402 | "icon": [ 403 | {"icon": ".white_ship"}, 404 | {"text": "CA", "pos": [100, 115], "fontsize": 30} 405 | ] 406 | }, 407 | "140114": { 408 | "names": ["junk", "dhow"], 409 | "civ": true, 410 | "icon": [ 411 | {"icon": ".white_ship"}, 412 | {"text": "QJ", "pos": [100, 115], "fontsize": 30} 413 | ] 414 | }, 415 | "140115": { 416 | "names": ["civilian barge, not self-propelled", "barge, not self-propelled", "barge"], 417 | "civ": true, 418 | "icon": [ 419 | {"icon": ".white_ship"}, 420 | {"text": "YB", "pos": [100, 115], "fontsize": 30} 421 | ] 422 | }, 423 | "140116": { 424 | "names": ["civilian hospital ship", "hospital ship"], 425 | "civ": true, 426 | "icon": [ 427 | {"icon": ".white_ship"}, 428 | {"fill": true, "stroke": false, "d": "m 95,95 0,-15 10,0 0,15 15,0 0,10 -15,0 0,15 -10,0 0,-15 -15,0 0,-10 z"} 429 | ] 430 | }, 431 | 432 | 433 | 434 | "140200": { 435 | "names": ["fishing vessel", "fishing boat", "fisherman"], 436 | "civ": true, 437 | "icon": [{"fill": "white", "d": "m 75,100 0,-15 20,0 0,15 50,0 -15,35 -60,0 -15,-35 z M 105,57.4 105,100 m 30,-35 -30,35"}] 438 | }, 439 | "140201": { 440 | "names": ["drifter"], 441 | "civ": true, 442 | "icon": [ 443 | {"icon": "140200"}, 444 | {"text": "DF", "pos": [100, 125], "fontsize": 30} 445 | ] 446 | }, 447 | "140202": { 448 | "names": ["trawler"], 449 | "civ": true, 450 | "icon": [ 451 | {"icon": "140200"}, 452 | {"text": "TR", "pos": [100, 125], "fontsize": 30} 453 | ] 454 | }, 455 | "140203": { 456 | "names": ["dredger"], 457 | "civ": true, 458 | "icon": [ 459 | {"icon": "140200"}, 460 | {"text": "DR", "pos": [100, 125], "fontsize": 30} 461 | ] 462 | }, 463 | "140300": { 464 | "names": ["law enforcement vessel"], 465 | "civ": true, 466 | "icon": [ 467 | {"fill": "white", "d": "m 75,100 0,-35 50,0 0,35 20,0 -15,35 -60,0 -15,-35 z"}, 468 | {"d": "m 135,100 -15,35 -10,0 15,-35 z", "fill": "icon"} 469 | ] 470 | }, 471 | "140400": { 472 | "names": ["leisure craft, sailing", "sailboat"], 473 | "civ": true, 474 | "icon": [{"fill": "white", "d": "m 105,55 0,40 35,0 z m -5,-5 0,50 m 45,0 -15,35 -60,0 -15,-35 z"}] 475 | }, 476 | "140500": { 477 | "names": ["leisure craft, motorized"], 478 | "civ": true, 479 | "icon": [{"fill": "white", "d": "m 70,97.4 15,-30 10,0 -15,30 65,0 -15,35 -60,0 -15,-35 z"}] 480 | }, 481 | "140501": { 482 | "names": ["civilian rigid-hull inflatable boat", "civilian RHIB", "rigid-hull inflatable boat", "RHIB"], 483 | "civ": true, 484 | "icon": [ 485 | {"icon": "140500"}, 486 | {"text": "RB", "pos": [100, 125], "fontsize": 30} 487 | ] 488 | }, 489 | "140502": { 490 | "names": ["speed boat", "speedboat"], 491 | "civ": true, 492 | "icon": [ 493 | {"icon": "140500"}, 494 | {"text": "SP", "pos": [100, 125], "fontsize": 30} 495 | ] 496 | }, 497 | "140600": { 498 | "names": ["civilian jet ski", "civilian jetski", "jet ski", "jetski"], 499 | "civ": true, 500 | "icon": [{"fill": "white", "d": "m 85,60 -30,45 10,15 75,0 0,-20 -60,0 10,-30 5,0 0,-10 z"}] 501 | }, 502 | "140700": { 503 | "names": ["unmanned surface water vehicle", "USV"], 504 | "civ": true, 505 | "icon": [{"fill": "white", "d": "m 60,84 40,20 40,-20 0,8 -40,25 -40,-25 z"}] 506 | }, 507 | 508 | 509 | 510 | 511 | "150000": { 512 | "names": ["own ship"], 513 | "icon": [{"text": "@TODO"}] 514 | }, 515 | "160000": { 516 | "names": ["fused track"], 517 | "icon": [ 518 | {"d": "m 70,65 10,35 -10,35 60,0 -10,-35 10,-35 z"}, 519 | {"text": "?"} 520 | ], 521 | "force_pending": true 522 | }, 523 | "170000": { 524 | "names": ["manual track"], 525 | "icon": [{"text": "MAN"}] 526 | } 527 | }, 528 | "M1": { 529 | "01": { 530 | "names": ["own ship"], 531 | "cat": "mission area", 532 | "icon": [{"textm1": "OWN"}] 533 | }, 534 | "02": { 535 | "names": ["antiair warfare"], 536 | "cat": "mission area", 537 | "icon": [{"textm1": "AAW"}] 538 | }, 539 | "03": { 540 | "names": ["antisubmarine warfare"], 541 | "cat": "mission area", 542 | "until": "2525E", 543 | "new": "C-1-151", 544 | "icon": [{"textm1": "ASW"}] 545 | }, 546 | "04": { 547 | "names": ["escort"], 548 | "cat": "mission area", 549 | "until": "2525E", 550 | "new": "C-1-152", 551 | "icon": [{"textm1": "E"}] 552 | }, 553 | "05": { 554 | "names": ["electronic warfare"], 555 | "cat": "mission area", 556 | "icon": [{"textm1": "EW"}] 557 | }, 558 | "06": { 559 | "names": ["intelligence, surveillance, and reconnaissance"], 560 | "cat": "mission area", 561 | "until": "2525E", 562 | "new": "C-1-125", 563 | "icon": [{"textm1": "IR"}] 564 | }, 565 | "07": { 566 | "names": ["mine countermeasures"], 567 | "cat": "mission area", 568 | "until": "2525E", 569 | "new": "C-1-153", 570 | "icon": [{"textm1": "MCM"}] 571 | }, 572 | "08": { 573 | "names": ["missile defense"], 574 | "icon": [{"textm1": "MD"}] 575 | }, 576 | "09": { 577 | "names": ["medical"], 578 | "cat": "mission area", 579 | "until": "2525E", 580 | "new": "C-1-127", 581 | "icon": [{"textm1": "ME"}] 582 | }, 583 | "10": { 584 | "names": ["mine warfare"], 585 | "cat": "mission area", 586 | "until": "2525E", 587 | "new": "C-1-154", 588 | "icon": [{"textm1": "MIW"}] 589 | }, 590 | "11": { 591 | "names": ["remote multi-mission vehicle"], 592 | "icon": [{"textm1": "RMV"}] 593 | }, 594 | "12": { 595 | "names": ["special operations forces"], 596 | "cat": "mission area", 597 | "until": "2525E", 598 | "new": "C-1-131", 599 | "icon": [{"textm1": "SOF"}] 600 | }, 601 | "13": { 602 | "names": ["surface warfare"], 603 | "cat": "mission area", 604 | "until": "2525E", 605 | "new": "C-1-155", 606 | "icon": [{"textm1": "SUW"}] 607 | }, 608 | "14": { 609 | "names": ["ballistic missile"], 610 | "cat": "capability", 611 | "until": "2525E", 612 | "new": "C-1-108", 613 | "icon": [{"textm1": "B"}] 614 | }, 615 | "15": { 616 | "names": ["guided missile"], 617 | "cat": "capability", 618 | "until": "2525E", 619 | "new": "C-1-133", 620 | "icon": [{"textm1": "G"}] 621 | }, 622 | "16": { 623 | "names": ["other guided missile"], 624 | "cat": "capability", 625 | "until": "2525E", 626 | "new": "C-1-134", 627 | "icon": [{"textm1": "M"}] 628 | }, 629 | "17": { 630 | "names": ["torpedo"], 631 | "cat": "capability", 632 | "icon": [{"textm1": "T"}] 633 | }, 634 | "18": { 635 | "names": ["drone-equipped"], 636 | "cat": "capability", 637 | "icon": [{"fill": true, "stroke": false, "d": "m 80,65 20,13 20,-13 0,-5 -20,10 -20,-10 z"}] 638 | }, 639 | "19": { 640 | "names": ["helicopter-equipped", "VSTOL equipped", "VSTOL capable"], 641 | "cat": "capability", 642 | "icon": [{"textm1": "H"}] 643 | }, 644 | "20": { 645 | "names": ["ballistic missile defense, shooter"], 646 | "cat": "capability", 647 | "until": "2525E", 648 | "new": "C-1-108", 649 | "icon": [{"textm1": "BM"}] 650 | }, 651 | "21": { 652 | "names": ["ballistic missile defense, long-range surveillance and track"], 653 | "cat": "capability", 654 | "until": "2525E", 655 | "new": "C-1-108", 656 | "icon": [{"textm1": "ST"}] 657 | }, 658 | "22": { 659 | "names": ["sea-based X-band"], 660 | "cat": "capability", 661 | "icon": [{"textm1": "SBX"}] 662 | }, 663 | "23": { 664 | "names": ["hijacking"], 665 | "cat": "crime", 666 | "until": "2525E", 667 | "new": "C-1-150", 668 | "icon": [{"textm1": "H"}] 669 | }, 670 | "24": { 671 | "names": ["hijacker"], 672 | "cat": "crime", 673 | "until": "2525E", 674 | "new": "C-1-150", 675 | "icon": [{"textm1": "HJ"}] 676 | }, 677 | "25": { 678 | "names": ["cyberspace"], 679 | "cat": "capability", 680 | "until": "2525E", 681 | "new": "C-1-115", 682 | "icon": [{"textm1": "CYB"}] 683 | } 684 | }, 685 | "M2": { 686 | "01": { 687 | "names": ["nuclear-powered"], 688 | "cat": "ship propulsion", 689 | "icon": [{"textm2": "N"}] 690 | }, 691 | "02": { 692 | "names": ["heavy"], 693 | "cat": "capability", 694 | "until": "2525E", 695 | "new": "C-2-118", 696 | "icon": [{"textm2": "H"}] 697 | }, 698 | "03": { 699 | "names": ["light"], 700 | "cat": "capability", 701 | "until": "2525E", 702 | "new": "C-2-121", 703 | "icon": [{"textm2": "L"}] 704 | }, 705 | "04": { 706 | "names": ["medium"], 707 | "cat": "capability", 708 | "until": "2525E", 709 | "new": "C-2-119", 710 | "icon": [{"textm2": "M"}] 711 | }, 712 | "05": { 713 | "names": ["dock"], 714 | "cat": "cargo capacity", 715 | "icon": [{"textm2": "D"}] 716 | }, 717 | "06": { 718 | "names": ["logistics"], 719 | "cat": "cargo capacity", 720 | "icon": [{"textm2": "LOG"}] 721 | }, 722 | "07": { 723 | "names": ["tank"], 724 | "cat": "cargo capacity", 725 | "icon": [{"textm2": "T"}] 726 | }, 727 | "08": { 728 | "names": ["vehicle"], 729 | "cat": "cargo capacity", 730 | "icon": [{"textm2": "V"}] 731 | }, 732 | "09": { 733 | "names": ["fast"], 734 | "cat": "cargo capacity", 735 | "icon": [{"textm2": "F"}] 736 | }, 737 | "10": { 738 | "names": ["air-cushioned (US)"], 739 | "cat": "mobility", 740 | "icon": [{"textm2": "AC"}] 741 | }, 742 | "11": { 743 | "names": ["air-cushioned (NATO)"], 744 | "cat": "mobility", 745 | "icon": [{"textm2": "J"}] 746 | }, 747 | "12": { 748 | "names": ["hydrofoil"], 749 | "cat": "mobility", 750 | "icon": [{"textm2": "K"}] 751 | }, 752 | "13": { 753 | "names": ["autonomous control"], 754 | "cat": "capability", 755 | "until": "2525E", 756 | "new": "C-2-110", 757 | "icon": [{"textm2": "AUT"}] 758 | }, 759 | "14": { 760 | "names": ["remotely-piloted"], 761 | "cat": "capability", 762 | "until": "2525E", 763 | "new": "C-2-111", 764 | "icon": [{"textm2": "RP"}] 765 | }, 766 | "15": { 767 | "names": ["expendable"], 768 | "cat": "capability", 769 | "until": "2525E", 770 | "new": "C-2-112", 771 | "icon": [{"textm2": "USV"}] 772 | }, 773 | "16": { 774 | "names": ["cyberspace"], 775 | "cat": "capability", 776 | "until": "2525E", 777 | "new": "C-2-122", 778 | "icon": [{"textm2": "CYB"}] 779 | } 780 | } 781 | } -------------------------------------------------------------------------------- /src/military_symbol/schema/signals_intelligence.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "50", 3 | "name": "signals intelligence", 4 | "dimension": "space", 5 | "IC": { 6 | "110000": { 7 | "names": ["signal intercept"], 8 | "match weight": -1, 9 | "icon": [] 10 | }, 11 | "110100": { 12 | "names": ["comunications"], 13 | "modcats": ["land communications", "space communications", "air communications", "surface communications", "surface communications"], 14 | "icon": [ 15 | {"strokewidth": 4, "d": "M 100,30 C 90,30 80,35 68.65625,50 l 62.6875,0 C 120,35 110,30 100,30"}, 16 | {"d": "m 93,120 14,0 0,0 m -7,-27 0,27 m 2,-25 8,2 -5,5 13,3 m -20,-10 -8,2 5,5 -14,3 m 21,-13 8,-2 -5,-5 13,-3 m -37,0 14,3 -5,5 8,2"} 17 | ] 18 | }, 19 | "110200": { 20 | "names": ["jammer"], 21 | "modcats": ["land jammer", "space jammer", "air jammer", "surface jammer", "surface jammer"], 22 | "icon": [{"text": "J"}] 23 | }, 24 | "110300": { 25 | "names": ["radar"], 26 | "modcats": ["land radar", "space radar", "air radar", "surface radar", "surface radar"], 27 | "icon": [ 28 | {"strokewidth": 4, "d": "M 100,30 C 90,30 80,35 68.65625,50 l 62.6875,0 C 120,35 110,30 100,30"}, 29 | {"d": "m 115,90 -15,15 0,-15 -15,15 M 80,85 c 0,25 15,35 35,35"} 30 | ] 31 | } 32 | 33 | }, 34 | "M1": { 35 | "01": { 36 | "names": ["anti-aircraft fire control"], 37 | "cats": ["air radar", "land radar"], 38 | "icon": [{"textm1": "AA"}] 39 | }, 40 | "02": { 41 | "names": ["airborne search and bombing"], 42 | "cat": "air radar", 43 | "icon": [{"textm1": "AB"}] 44 | }, 45 | "03": { 46 | "names": ["airborne intercept"], 47 | "cat": "air radar", 48 | "icon": [{"textm1": "AI"}] 49 | }, 50 | "04": { 51 | "names": ["altimeter"], 52 | "cat": "air radar", 53 | "icon": [{"textm1": "AL"}] 54 | }, 55 | "05": { 56 | "names": ["airborne reconnaissance and mapping"], 57 | "cat": "air radar", 58 | "icon": [{"textm1": "AM"}] 59 | }, 60 | "06": { 61 | "names": ["air traffic control", "ATC"], 62 | "cats": ["air radar", "land radar", "surface radar"], 63 | "icon": [{"textm1": "AT"}] 64 | }, 65 | "07": { 66 | "names": ["beacon transponder not IFF"], 67 | "cats": ["air radar", "land radar", "surface radar", "subsurface radar"], 68 | "icon": [{"textm1": "BN"}] 69 | }, 70 | "08": { 71 | "names": ["battlefield surveillance"], 72 | "cats": ["air radar", "land radar"], 73 | "icon": [{"textm1": "BN"}] 74 | }, 75 | "09": { 76 | "names": ["controlled approach"], 77 | "cats": ["land radar", "surface radar"], 78 | "icon": [{"textm1": "CA"}] 79 | }, 80 | "10": { 81 | "names": ["controlled intercept"], 82 | "cats": ["air radar", "land radar", "surface radar"], 83 | "icon": [{"textm1": "CI"}] 84 | }, 85 | "11": { 86 | "names": ["cellular", "mobile"], 87 | "cats": ["land radar", "surface radar", "subsurface", "air radar"], 88 | "icon": [{"textm1": "CM"}] 89 | }, 90 | "12": { 91 | "names": ["coastal surveillance"], 92 | "cats": ["land radar"], 93 | "icon": [{"textm1": "CS"}] 94 | }, 95 | "13": { 96 | "names": ["decoy", "mimic"], 97 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar"], 98 | "icon": [{"textm1": "DC"}] 99 | }, 100 | "14": { 101 | "names": ["data transmission"], 102 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 103 | "icon": [{"textm1": "DT"}] 104 | }, 105 | "15": { 106 | "names": ["earth surveillance"], 107 | "cats": ["space radar"], 108 | "icon": [{"textm1": "ES"}] 109 | }, 110 | "16": { 111 | "names": ["early warning"], 112 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar"], 113 | "icon": [{"textm1": "DT"}] 114 | }, 115 | "17": { 116 | "names": ["fire control"], 117 | "cats": ["land radar", "surface radar", "air radar"], 118 | "icon": [{"textm1": "FC"}] 119 | }, 120 | "18": { 121 | "names": ["ground mapping"], 122 | "cats": ["air radar"], 123 | "icon": [{"textm1": "GM"}] 124 | }, 125 | "19": { 126 | "names": ["height finding"], 127 | "cats": ["land radar", "surface radar"], 128 | "icon": [{"textm1": "HF"}] 129 | }, 130 | "20": { 131 | "names": ["harbor surveillance"], 132 | "cats": ["land radar"], 133 | "icon": [{"textm1": "HS"}] 134 | }, 135 | "21": { 136 | "names": ["IFF interrogator", "identification friend or foe interrogator"], 137 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 138 | "icon": [{"textm1": "IF"}] 139 | }, 140 | "22": { 141 | "names": ["instrument landing system"], 142 | "cats": ["land radar", "surface radar"], 143 | "icon": [{"textm1": "IL"}] 144 | }, 145 | "23": { 146 | "names": ["ionospheric sounding"], 147 | "cats": ["land radar", "air radar"], 148 | "icon": [{"textm1": "IS"}] 149 | }, 150 | "24": { 151 | "names": ["IFF transponder", "identification friend or foe transponder"], 152 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 153 | "icon": [{"textm1": "IT"}] 154 | }, 155 | "25": { 156 | "names": ["barrage jammer"], 157 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 158 | "icon": [{"textm1": "JB"}] 159 | }, 160 | "26": { 161 | "names": ["click jammer"], 162 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 163 | "icon": [{"textm1": "JC"}] 164 | }, 165 | "27": { 166 | "names": ["deceptive jammer"], 167 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 168 | "icon": [{"textm1": "JD"}] 169 | }, 170 | "28": { 171 | "names": ["frequency swept jammer"], 172 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 173 | "icon": [{"textm1": "JF"}] 174 | }, 175 | "29": { 176 | "names": ["jammer", "general jammer", "jammer general"], 177 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 178 | "icon": [{"textm1": "JG"}] 179 | }, 180 | "30": { 181 | "names": ["noise jammer"], 182 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 183 | "icon": [{"textm1": "JN"}] 184 | }, 185 | "31": { 186 | "names": ["pulsed jammer"], 187 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 188 | "icon": [{"textm1": "JP"}] 189 | }, 190 | "32": { 191 | "names": ["repeater jammer"], 192 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 193 | "icon": [{"textm1": "JR"}] 194 | }, 195 | "33": { 196 | "names": ["spot noise jammer"], 197 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 198 | "icon": [{"textm1": "JS"}] 199 | }, 200 | "34": { 201 | "names": ["transponder jammer"], 202 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 203 | "icon": [{"textm1": "JT"}] 204 | }, 205 | "35": { 206 | "names": ["missile acquisition"], 207 | "cats": ["air radar", "surface radar", "land radar"], 208 | "icon": [{"textm1": "JN"}] 209 | }, 210 | "36": { 211 | "names": ["missile control"], 212 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 213 | "icon": [{"textm1": "MC"}] 214 | }, 215 | "37": { 216 | "names": ["missile downlink"], 217 | "cats": ["air radar"], 218 | "icon": [{"textm1": "MD"}] 219 | }, 220 | "38": { 221 | "names": ["meteorological"], 222 | "cats": ["land radar", "surface radar", "air radar"], 223 | "icon": [{"textm1": "ME"}] 224 | }, 225 | "39": { 226 | "names": ["multifunction", "multi-function", "multi function"], 227 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 228 | "icon": [{"textm1": "JT"}] 229 | }, 230 | "40": { 231 | "names": ["missile guidance"], 232 | "cats": ["land radar", "surface radar", "air radar"], 233 | "icon": [{"textm1": "MG"}] 234 | }, 235 | "41": { 236 | "names": ["missile homing"], 237 | "cats": ["land radar", "surface radar", "air radar"], 238 | "icon": [{"textm1": "MH"}] 239 | }, 240 | "42": { 241 | "names": ["missile tracking"], 242 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 243 | "icon": [{"textm1": "MT"}] 244 | }, 245 | "43": { 246 | "names": ["navigational", "navigational general"], 247 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 248 | "icon": [{"textm1": "NA"}] 249 | }, 250 | "44": { 251 | "names": ["navigational distance measuring", "distance measuring"], 252 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 253 | "icon": [{"textm1": "ND"}] 254 | }, 255 | "45": { 256 | "names": ["navigational terrain following", "terrain following"], 257 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 258 | "icon": [{"textm1": "NT"}] 259 | }, 260 | "46": { 261 | "names": ["navigational weather avoidance", "weather avoidance"], 262 | "cats": ["land jammer", "surface jammer", "air jammer", "subsurface jammer", "space jammer"], 263 | "icon": [{"textm1": "NW"}] 264 | }, 265 | 266 | 267 | "47": { 268 | "names": ["omni line of sight", "omni LOS"], 269 | "cats": ["land communications", "surface communications", "air communications", "subsurface communications", "space communications"], 270 | "icon": [{"textm1": "OL"}] 271 | }, 272 | "48": { 273 | "names": ["proximity fuse"], 274 | "cats": ["air radar"], 275 | "icon": [{"textm1": "PF"}] 276 | }, 277 | "49": { 278 | "names": ["point to point line of sight", "point to point LOS"], 279 | "cats": ["land communications", "surface communications", "air communications", "subsurface communications", "space communications"], 280 | "icon": [{"textm1": "PP"}] 281 | }, 282 | "50": { 283 | "names": ["instrumentation"], 284 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 285 | "icon": [{"textm1": "RI"}] 286 | }, 287 | "51": { 288 | "names": ["range only"], 289 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 290 | "icon": [{"textm1": "RO"}] 291 | }, 292 | "52": { 293 | "names": ["sonobuoy"], 294 | "cats": ["surface radar", "subsurface radar"], 295 | "icon": [{"textm1": "SB"}] 296 | }, 297 | "53": { 298 | "names": ["satellite downlink"], 299 | "cats": ["space communications"], 300 | "icon": [{"textm1": "SD"}] 301 | }, 302 | "54": { 303 | "names": ["space"], 304 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 305 | "icon": [{"textm1": "SP"}] 306 | }, 307 | "55": { 308 | "names": ["surface search"], 309 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 310 | "icon": [{"textm1": "SS"}] 311 | }, 312 | "56": { 313 | "names": ["shell tracking"], 314 | "cats": ["land radar"], 315 | "icon": [{"textm1": "ST"}] 316 | }, 317 | "57": { 318 | "names": ["satellite uplink"], 319 | "cats": ["land communications", "surface communications", "air communications", "subsurface communications"], 320 | "icon": [{"textm1": "PP"}] 321 | }, 322 | "58": { 323 | "names": ["target acquisition"], 324 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 325 | "icon": [{"textm1": "TA"}] 326 | }, 327 | "59": { 328 | "names": ["target illumination"], 329 | "cats": ["land radar", "surface radar", "air radar"], 330 | "icon": [{"textm1": "TI"}] 331 | }, 332 | "60": { 333 | "names": ["tropospheric scatter"], 334 | "cats": ["land communications"], 335 | "icon": [{"textm1": "TS"}] 336 | }, 337 | "61": { 338 | "names": ["target tracking"], 339 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 340 | "icon": [{"textm1": "TT"}] 341 | }, 342 | "62": { 343 | "names": ["unknown"], 344 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 345 | "icon": [{"textm1": "?"}] 346 | }, 347 | "63": { 348 | "names": ["video remoting"], 349 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 350 | "icon": [{"textm1": "VR"}] 351 | }, 352 | "64": { 353 | "names": ["experimental"], 354 | "cats": ["land radar", "surface radar", "air radar", "subsurface radar", "space radar"], 355 | "icon": [{"textm1": "TA"}] 356 | } 357 | } 358 | } 359 | 360 | -------------------------------------------------------------------------------- /src/military_symbol/schema/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "05", 3 | "name": "space", 4 | "dimension": "space", 5 | "IC": { 6 | "110000": { 7 | "names": ["military space platform", "military"], 8 | "modcats": ["capability", "orbit", "sensor"], 9 | "icon": [{"text": "MIL"}] 10 | }, 11 | "110100": { 12 | "names": ["military space vehicle", "space vehicle"], 13 | "modcats": ["capability", "orbit", "sensor"], 14 | "icon": [{"text": "SV"}] 15 | }, 16 | "110200": { 17 | "names": ["military re-entry vehicle", "re-entry vehicle"], 18 | "modcats": ["capability", "orbit", "sensor"], 19 | "icon": [{"text": "RV"}] 20 | }, 21 | "110300": { 22 | "names": ["planet lander", "military planet lander", "planetary lander"], 23 | "modcats": ["capability", "orbit", "sensor"], 24 | "icon": [{"text": "PL"}] 25 | }, 26 | "110400": { 27 | "names": ["orbiter shuttle"], 28 | "modcats": ["capability", "orbit", "sensor"], 29 | "icon": [{"fill": true, "d": "m 89,115 6,-25 c 3,-12 7,-12 10,0 l 6,25 -10,0 -1,5 -1,-5 z"}] 30 | }, 31 | "110500": { 32 | "names": ["capsule"], 33 | "modcats": ["capability", "orbit", "sensor"], 34 | "icon": [{"fill": true, "d": "m 85,115 c -2,5 32,5 30,0 l -5,-30 c -1,-5 -19,-5 -20,0 z"}] 35 | }, 36 | "110600": { 37 | "names": ["general satellite"], 38 | "modcats": ["capability", "orbit", "sensor"], 39 | "icon": [{"text": "SAT"}] 40 | }, 41 | "110700": { 42 | "names": ["satellite"], 43 | "modcats": ["capability", "orbit", "sensor"], 44 | "icon": [{"fill": true, "d": "m 110,100 10,0 m -40,0 10,0 m -10,-10 -25,0 0,20 25,0 z m 40,0 0,20 25,0 0,-20 z m -30,0 0,20 20,0 0,-20 z"}] 45 | }, 46 | "110800": { 47 | "names": ["anti-satellite weapon", "antisatellite weapon"], 48 | "modcats": ["capability", "antisatellite capability", "orbit", "sensor"], 49 | "icon": [{"fill": true, "d": "m 100,110 0,9 m 0,-34 0,5 m 0,-9 -2,4 4,0 z m -10,9 0,20 20,0 0,-20 z m 25,0 0,20 25,0 0,-20 z m -30,0 -25,0 0,20 25,0 z m 0,10 5,0 m 20,0 5,0"}] 50 | }, 51 | "110900": { 52 | "names": ["astronomical satellite"], 53 | "modcats": ["capability", "orbit", "sensor"], 54 | "icon": [{"fill": true, "d": "m 97,90 -1,-9 8,0 -1,9 m -5,20 1,9 2,0 1,-9 m 8,-10 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}] 55 | }, 56 | "111000": { 57 | "names": ["biosatellite"], 58 | "modcats": ["capability", "orbit", "sensor"], 59 | "icon": [{"fill": true, "d": "m 100,89 c 0,4.4 -3.6,8 -8,8 -4.4,0 -8,-3.6 -8,-8 0,-4.4 3.6,-8 8,-8 4.4,0 8,3.6 8,8 z m -10,10 0,20 20,0 0,-20 z m 25,0 0,20 25,0 0,-20 z m -30,0 -25,0 0,20 25,0 z m 0,10 5,0 m 20,0 5,0 m -17,-25 17,10 -1,2 -14,-7"}] 60 | }, 61 | "111100": { 62 | "names": ["communications satellite"], 63 | "modcats": ["capability", "orbit", "sensor"], 64 | "icon": [ 65 | {"fill": true, "d": "m 110,109 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}, 66 | {"d": "m 100,90 0,9 M 75,81 c 16,12 34,12 50,0"} 67 | ] 68 | }, 69 | "111200": { 70 | "names": ["earth observation satellite"], 71 | "modcats": ["capability", "orbit", "sensor"], 72 | "icon": [ 73 | {"fill": true, "d": "m 107,113 c 0,3.9 -3.1,7 -7,7 -3.9,0 -7,-3.1 -7,-7 0,-3.9 3.1,-7 7,-7 3.9,0 7,3.1 7,7 z m -17,-33 0,20 20,0 0,-20 z m 25,0 0,20 25,0 0,-20 z m -30,0 -25,0 0,20 25,0 z m 0,10 5,0 m 20,0 5,0"}, 74 | {"d": "m 88,107 c 8,-9 16,-9 24,0"} 75 | ] 76 | }, 77 | "111300": { 78 | "names": ["miniaturized satellite"], 79 | "modcats": ["capability", "orbit", "sensor"], 80 | "icon": [ 81 | {"fill": true, "d": "m 91.1,92 0,16 17.8,0 0,-16 z m 22.2,0 0,16 22.2,0 0,-16 z m -26.6,0 -22.2,0 0,16 22.2,0 z m 0,8 4.4,0 m 17.8,0 4.4,0"}, 82 | {"d": "m 90,119 10,-9 10,9 m -20,-38 10,9 10,-9 m 35,9 -10,10 10,10 M 55,90 65,100 55,110"} 83 | ] 84 | }, 85 | "111400": { 86 | "names": ["navigational satellite"], 87 | "modcats": ["capability", "orbit", "sensor"], 88 | "icon": [ 89 | {"fill": true, "d": "m 110,109 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}, 90 | {"d": "m 88,87 c 8,6 16,6 24,0 m -20,8 8,-14 8,14"} 91 | ] 92 | }, 93 | "111500": { 94 | "names": ["reconnaissance satellite"], 95 | "modcats": ["capability", "orbit", "sensor"], 96 | "icon": [ 97 | {"fill": true, "d": "m 106,100 9,20 m -21,-20 -9,20 m 17,-20 3,20 m -7,-20 -3,20 m 15,-30 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"} 98 | ] 99 | }, 100 | "111600": { 101 | "names": ["space station"], 102 | "modcats": ["capability", "orbit", "sensor"], 103 | "icon": [{"fill": true, "d": "m 97.5,112.5 0,7.5 5,0 0,-7.5 z m 0,-32.5 5,0 0,26.4 -5,0 z m -0.3,7.6 C 83.3,88.2 72.5,93.5 72.5,100 c 0,6.9 12.3,12.5 27.5,12.5 15.2,0 27.5,-5.6 27.5,-12.5 0,-6.5 -11,-11.9 -25,-12.4 l 0,5.6 c 9.9,0.4 17.5,3.2 17.5,6.6 0,3.7 -8.9,6.7 -19.8,6.7 -10.9,0 -19.8,-3 -19.8,-6.7 0,-3.4 7.4,-6.1 17.1,-6.6 l 0,-5.6 c -0.1,0 -0.2,-0 -0.3,0 z"}] 104 | }, 105 | "111700": { 106 | "names": ["tethered satellite"], 107 | "modcats": ["capability", "orbit", "sensor"], 108 | "icon": [{"fill": true, "d": "m 120,87 -20,12 m 33,-12 c 0,3.6 -2.9,6.5 -6.5,6.5 -3.6,0 -6.5,-2.9 -6.5,-6.5 0,-3.6 2.9,-6.5 6.5,-6.5 3.6,0 6.5,2.9 6.5,6.5 z m -23,22 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}] 109 | }, 110 | "111800": { 111 | "names": ["weather satellite"], 112 | "modcats": ["capability", "orbit", "sensor"], 113 | "icon": [ 114 | {"fill": true, "d": "m 110,109 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}, 115 | {"text": "WX", "pos": [100, 100], "fontsize": 25} 116 | ] 117 | }, 118 | "111900": { 119 | "names": ["space-launched vehicle", "space launched vehicle", "SLV"], 120 | "modcats": ["capability", "orbit", "sensor"], 121 | "icon": [{"text": "SLV"}] 122 | }, 123 | 124 | "120000": { 125 | "names": ["civilian space"], 126 | "civ": true, 127 | "modcats": ["capability", "orbit", "sensor"], 128 | "icon": [{"text": "CIV", "fill": "white", "stroke": true}] 129 | }, 130 | "120100": { 131 | "names": ["civilian orbiter shuttle"], 132 | "civ": true, 133 | "modcats": ["capability", "orbit", "sensor"], 134 | "icon": [{"fill": "white", "d": "m 89,115 6,-25 c 3,-12 7,-12 10,0 l 6,25 -10,0 -1,5 -1,-5 z"}] 135 | }, 136 | "120200": { 137 | "names": ["civilian capsule"], 138 | "civ": true, 139 | "modcats": ["capability", "orbit", "sensor"], 140 | "icon": [{"fill": "white", "d": "m 85,115 c -2,5 32,5 30,0 l -5,-30 c -1,-5 -19,-5 -20,0 z"}] 141 | }, 142 | "120300": { 143 | "names": ["civilian satellite"], 144 | "civ": true, 145 | "modcats": ["capability", "orbit", "sensor"], 146 | "icon": [{"fill": "white", "d": "m 110,100 10,0 m -40,0 10,0 m -10,-10 -25,0 0,20 25,0 z m 40,0 0,20 25,0 0,-20 z m -30,0 0,20 20,0 0,-20 z"}] 147 | }, 148 | "120400": { 149 | "names": ["civilian astronomical satellite"], 150 | "civ": true, 151 | "modcats": ["capability", "orbit", "sensor"], 152 | "icon": [{"fill": "white", "d": "m 97,90 -1,-9 8,0 -1,9 m -5,20 1,9 2,0 1,-9 m 8,-10 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}] 153 | }, 154 | "120500": { 155 | "names": ["civilian biosatellite"], 156 | "civ": true, 157 | "modcats": ["capability", "orbit", "sensor"], 158 | "icon": [{"fill": "white", "d": "m 100,89 c 0,4.4 -3.6,8 -8,8 -4.4,0 -8,-3.6 -8,-8 0,-4.4 3.6,-8 8,-8 4.4,0 8,3.6 8,8 z m -10,10 0,20 20,0 0,-20 z m 25,0 0,20 25,0 0,-20 z m -30,0 -25,0 0,20 25,0 z m 0,10 5,0 m 20,0 5,0 m -17,-25 17,10 -1,2 -14,-7"}] 159 | }, 160 | "120600": { 161 | "names": ["civilian communications satellite"], 162 | "civ": true, 163 | "modcats": ["capability", "orbit", "sensor"], 164 | "icon": [ 165 | {"fill": "white", "d": "m 110,109 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}, 166 | {"d": "m 100,90 0,9 M 75,81 c 16,12 34,12 50,0"} 167 | ] 168 | }, 169 | "120700": { 170 | "names": ["civilian earth observation satellite"], 171 | "civ": true, 172 | "modcats": ["capability", "orbit", "sensor"], 173 | "icon": [ 174 | {"fill": "white", "d": "m 107,113 c 0,3.9 -3.1,7 -7,7 -3.9,0 -7,-3.1 -7,-7 0,-3.9 3.1,-7 7,-7 3.9,0 7,3.1 7,7 z m -17,-33 0,20 20,0 0,-20 z m 25,0 0,20 25,0 0,-20 z m -30,0 -25,0 0,20 25,0 z m 0,10 5,0 m 20,0 5,0"}, 175 | {"d": "m 88,107 c 8,-9 16,-9 24,0"} 176 | ] 177 | }, 178 | "120800": { 179 | "names": ["civilian miniaturized satellite"], 180 | "civ": true, 181 | "modcats": ["capability", "orbit", "sensor"], 182 | "icon": [ 183 | {"fill": "white", "d": "m 91.1,92 0,16 17.8,0 0,-16 z m 22.2,0 0,16 22.2,0 0,-16 z m -26.6,0 -22.2,0 0,16 22.2,0 z m 0,8 4.4,0 m 17.8,0 4.4,0"}, 184 | {"d": "m 90,119 10,-9 10,9 m -20,-38 10,9 10,-9 m 35,9 -10,10 10,10 M 55,90 65,100 55,110"} 185 | ] 186 | }, 187 | "120900": { 188 | "names": ["civilian navigational satellite"], 189 | "civ": true, 190 | "modcats": ["capability", "orbit", "sensor"], 191 | "icon": [ 192 | {"fill": "white", "d": "m 110,109 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}, 193 | {"d": "m 88,87 c 8,6 16,6 24,0 m -20,8 8,-14 8,14"} 194 | ] 195 | }, 196 | "121000": { 197 | "names": ["civilian space station"], 198 | "civ": true, 199 | "modcats": ["capability", "orbit", "sensor"], 200 | "icon": [ 201 | {"fill": "white", "d": "m 97.5,112.5 0,7.5 5,0 0,-7.5 z m 0,-32.5 5,0 0,26.4 -5,0 z m -0.3,7.6 C 83.3,88.2 72.5,93.5 72.5,100 c 0,6.9 12.3,12.5 27.5,12.5 15.2,0 27.5,-5.6 27.5,-12.5 0,-6.5 -11,-11.9 -25,-12.4 l 0,5.6 c 9.9,0.4 17.5,3.2 17.5,6.6 0,3.7 -8.9,6.7 -19.8,6.7 -10.9,0 -19.8,-3 -19.8,-6.7 0,-3.4 7.4,-6.1 17.1,-6.6 l 0,-5.6 c -0.1,0 -0.2,-0 -0.3,0 z"} 202 | ] 203 | }, 204 | "121100": { 205 | "names": ["civilian tethered satellite"], 206 | "civ": true, 207 | "modcats": ["capability", "orbit", "sensor"], 208 | "icon": [ 209 | {"fill": "white", "d": "m 120,87 -20,12 m 33,-12 c 0,3.6 -2.9,6.5 -6.5,6.5 -3.6,0 -6.5,-2.9 -6.5,-6.5 0,-3.6 2.9,-6.5 6.5,-6.5 3.6,0 6.5,2.9 6.5,6.5 z m -23,22 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"} 210 | ] 211 | }, 212 | "121200": { 213 | "names": ["civilian weather satellite"], 214 | "civ": true, 215 | "modcats": ["capability", "orbit", "sensor"], 216 | "icon": [ 217 | {"fill": "white", "d": "m 110,109 5,0 m -30,0 5,0 m -5,-10 -25,0 0,20 25,0 z m 30,0 0,20 25,0 0,-20 z m -25,0 0,20 20,0 0,-20 z"}, 218 | {"text": "WX", "pos": [100, 100], "fontsize": 25} 219 | ] 220 | }, 221 | 222 | "121300": { 223 | "names": ["civilian planetary lander"], 224 | "civ": true, 225 | "modcats": ["capability", "orbit", "sensor"], 226 | "icon": [{"text": "PL", "fill": "white", "stroke": true}] 227 | }, 228 | "121300": { 229 | "names": ["civilian space vehicle"], 230 | "civ": true, 231 | "modcats": ["capability", "orbit", "sensor"], 232 | "icon": [{"text": "SV", "fill": "white", "stroke": true}] 233 | }, 234 | 235 | "130000": { 236 | "names": ["manual track"], 237 | "icon": [{"text": "MAN"}] 238 | } 239 | 240 | }, 241 | "M1": { 242 | "01": { 243 | "names": ["low earth orbit", "LEO"], 244 | "cat": "orbit", 245 | "icon": [{"textm1": "LEO"}] 246 | }, 247 | "02": { 248 | "names": ["medium earth orbit", "MEO"], 249 | "cat": "orbit", 250 | "icon": [{"textm1": "MEO"}] 251 | }, 252 | "03": { 253 | "names": ["high earth orbit", "HEO"], 254 | "cat": "orbit", 255 | "icon": [{"textm1": "HEO"}] 256 | }, 257 | "04": { 258 | "names": ["geosynchronous orbit", "GSO"], 259 | "cat": "orbit", 260 | "icon": [{"textm1": "GSO"}] 261 | }, 262 | "05": { 263 | "names": ["geostationary orbit", "GO"], 264 | "cat": "orbit", 265 | "icon": [{"textm1": "GO"}] 266 | }, 267 | "06": { 268 | "names": ["Molniya orbit", "MO"], 269 | "cat": "orbit", 270 | "icon": [{"textm1": "MO"}] 271 | }, 272 | "07": { 273 | "names": ["cyberspace", "cyber"], 274 | "cat": "capability", 275 | "icon": [{"textm1": "CYB"}], 276 | "new": "C-1-115" 277 | } 278 | }, 279 | "M2": { 280 | "01": { 281 | "names": ["optical"], 282 | "cat": "sensor", 283 | "icon": [{"textm2": "O"}] 284 | }, 285 | "02": { 286 | "names": ["infrared"], 287 | "cat": "sensor", 288 | "icon": [{"textm2": "IR"}] 289 | }, 290 | "03": { 291 | "names": ["radar"], 292 | "cat": "sensor", 293 | "icon": [{"textm2": "R"}] 294 | }, 295 | "04": { 296 | "names": ["signals intelligence"], 297 | "cat": "sensor", 298 | "icon": [{"textm2": "SI"}] 299 | }, 300 | "05": { 301 | "names": ["cyberspace", "cyber"], 302 | "cat": "capability", 303 | "new": "C-2-122", 304 | "icon": [{"textm2": "CYB"}] 305 | }, 306 | "06": { 307 | "names": ["electromagnetic warfare ASAT", "EW ASAT"], 308 | "cat": "capability", 309 | "icon": [{"textm2": "EW"}] 310 | }, 311 | "07": { 312 | "names": ["high power microwave ASAT", "HPM ASAT"], 313 | "cat": "capability", 314 | "icon": [{"textm2": "EW"}] 315 | }, 316 | "08": { 317 | "names": ["laser ASAT", "HPM ASAT"], 318 | "cat": "antisatellite capability", 319 | "icon": [{"d": "m 130,129 h -17 l -3,7 -3,-13 -4,13 -3.1,-13 -3.4,6 h -9.9 l -3.3,7 -3.3,-13 -3.3,13 -3.3,-13 -3.3,13 m 52.9,-13 7,6 -7,7"}] 320 | }, 321 | "09": { 322 | "names": ["maintenance"], 323 | "cat": "antisatellite capability", 324 | "icon": [{"d": "M75,125 c8,0 8,16 0,16 m8,-8 l35,0 m8,-8 c-8,0 -8,16 0,16"}] 325 | }, 326 | "10": { 327 | "names": ["mine"], 328 | "cat": "capability", 329 | "icon": [ 330 | { 331 | "d": "m 111,133 c 0,4 -5,6 -11,6 -6.3,0 -11.4,-2 -11.4,-6 0,-2 5.1,-5 11.4,-5 6,0 11,3 11,5 z m -2,-11 -17.5,23 m 0,-23 17.5,23 m -9,-23 v 23", 332 | "fill": true 333 | } 334 | ] 335 | }, 336 | "11": { 337 | "names": ["refuel", "refueling"], 338 | "cat": "capability", 339 | "icon": [{"textm2": "K"}] 340 | }, 341 | "12": { 342 | "names": ["tug"], 343 | "cat": "capability", 344 | "icon": [{"textm2": "tug"}] 345 | } 346 | } 347 | } 348 | 349 | -------------------------------------------------------------------------------- /src/military_symbol/schema/space_missile.json: -------------------------------------------------------------------------------- 1 | { 2 | "set": "06", 3 | "name": "space missile", 4 | "dimension": "space", 5 | "IC": { 6 | "110000": { 7 | "names": ["missile", "space missile"], 8 | "modcats": ["launch origin", "missile class", "missile range", "missile type"], 9 | "icon": [ 10 | {"d": "M90,135 l0,-10 5,-5 0,-55 5,-5 5,5 0,55 5,5 0,10 -10,-10 z", "fill": "yellow"} 11 | ] 12 | } 13 | }, 14 | "M1": { 15 | "01": { 16 | "names": ["ballistic"], 17 | "cat": "launch origin", 18 | "icon": [{"text": "B", "pos": [68, 110], "fontsize": 30}] 19 | }, 20 | "02": { 21 | "names": ["space"], 22 | "cat": "launch origin", 23 | "icon": [ 24 | {"text": "S", "pos": [68, 95], "fontsize": 30}, 25 | {"text": "P", "pos": [68, 125], "fontsize": 30} 26 | ] 27 | }, 28 | "03": { 29 | "names": ["interceptor"], 30 | "cat": "missile class", 31 | "icon": [ 32 | {"text": "H", "pos": [68, 95], "fontsize": 30}, 33 | {"text": "V", "pos": [68, 125], "fontsize": 30} 34 | ] 35 | }, 36 | "04": { 37 | "names": ["hypersonic"], 38 | "cat": "missile class", 39 | "since": "2525E", 40 | "icon": [{"text": "I", "pos": [68, 110], "fontsize": 30}] 41 | } 42 | }, 43 | "M2": { 44 | "01": { 45 | "names": ["short range"], 46 | "cat": "missile range", 47 | "icon": [ 48 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 49 | {"text": "R", "pos": [132, 125], "fontsize": 30} 50 | ] 51 | }, 52 | "02": { 53 | "names": ["medium range"], 54 | "cat": "missile range", 55 | "icon": [ 56 | {"text": "M", "pos": [132, 95], "fontsize": 30}, 57 | {"text": "R", "pos": [132, 125], "fontsize": 30} 58 | ] 59 | }, 60 | "03": { 61 | "names": ["intermediate range"], 62 | "cat": "missile range", 63 | "icon": [ 64 | {"text": "I", "pos": [132, 95], "fontsize": 30}, 65 | {"text": "R", "pos": [132, 125], "fontsize": 30} 66 | ] 67 | }, 68 | "04": { 69 | "names": ["long range"], 70 | "cat": "missile range", 71 | "icon": [ 72 | {"text": "L", "pos": [132, 95], "fontsize": 30}, 73 | {"text": "R", "pos": [132, 125], "fontsize": 30} 74 | ] 75 | }, 76 | "05": { 77 | "names": ["intercontinental"], 78 | "cat": "missile range", 79 | "icon": [ 80 | {"text": "I", "pos": [132, 95], "fontsize": 30}, 81 | {"text": "C", "pos": [132, 125], "fontsize": 30} 82 | ] 83 | }, 84 | "06": { 85 | "names": ["Arrow"], 86 | "cat": "missile type", 87 | "icon": [ 88 | {"text": "A", "pos": [132, 95], "fontsize": 30}, 89 | {"text": "R", "pos": [132, 125], "fontsize": 30} 90 | ] 91 | }, 92 | "07": { 93 | "names": ["ground-based interceptor", "GBI"], 94 | "cat": "missile type", 95 | "icon": [ 96 | {"text": "G", "pos": [132, 110], "fontsize": 30} 97 | ] 98 | }, 99 | "08": { 100 | "names": ["Patriot"], 101 | "cat": "missile type", 102 | "icon": [ 103 | {"text": "P", "pos": [132, 110], "fontsize": 30} 104 | ] 105 | }, 106 | "09": { 107 | "names": ["standard missile - terminal phase", "SM-T"], 108 | "cat": "missile type", 109 | "icon": [ 110 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 111 | {"text": "T", "pos": [132, 125], "fontsize": 30} 112 | ] 113 | }, 114 | "10": { 115 | "names": ["standard missile - 3", "SM-3"], 116 | "cat": "missile type", 117 | "icon": [ 118 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 119 | {"text": "3", "pos": [132, 125], "fontsize": 30} 120 | ] 121 | }, 122 | "11": { 123 | "names": ["terminal high-altitude area defense", "THAAD"], 124 | "cat": "missile type", 125 | "icon": [ 126 | {"text": "T", "pos": [132, 110], "fontsize": 30} 127 | ] 128 | }, 129 | "12": { 130 | "names": ["space"], 131 | "cat": "missile type", 132 | "icon": [ 133 | {"text": "S", "pos": [132, 95], "fontsize": 30}, 134 | {"text": "P", "pos": [132, 125], "fontsize": 30} 135 | ] 136 | }, 137 | 138 | "13": { 139 | "names": ["close range"], 140 | "since": "2525E", 141 | "cat": "missile type", 142 | "icon": [ 143 | {"text": "C", "pos": [132, 95], "fontsize": 30}, 144 | {"text": "R", "pos": [132, 125], "fontsize": 30} 145 | ] 146 | }, 147 | "13": { 148 | "names": ["debris"], 149 | "since": "2525E", 150 | "cat": "missile type", 151 | "icon": [ 152 | {"text": "D", "pos": [132, 95], "fontsize": 30}, 153 | {"text": "B", "pos": [132, 125], "fontsize": 30} 154 | ] 155 | }, 156 | "08": { 157 | "names": ["unknown"], 158 | "since": "2525E", 159 | "cat": "missile type", 160 | "icon": [ 161 | {"text": "U", "pos": [132, 110], "fontsize": 30} 162 | ] 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /src/military_symbol/symbol.py: -------------------------------------------------------------------------------- 1 | from schema import * 2 | from output_style import OutputStyle 3 | import re 4 | import os 5 | from drawing_items import BBox 6 | import math 7 | 8 | class Symbol(): 9 | def __init__(self, schema=None): 10 | self.schema = schema 11 | self.context:Context = None 12 | self.affiliation:Affiliation = None 13 | self.status:Status = None 14 | self.hqtfd:HQTFD = None 15 | self.amplifier:Amplifier = None 16 | self.symbol_set:SymbolSet = None 17 | self.entity:Entity = None 18 | self.modifier_1:Modifier = None 19 | self.modifier_2:Modifier = None 20 | 21 | self.frame_shape_override = None 22 | 23 | def is_valid(self) -> bool: 24 | return self.schema is not None 25 | 26 | def is_headquarters(self) -> bool: 27 | return self.hqtfd is not None and self.hqtfd.headquarters 28 | 29 | def is_task_force(self) -> bool: 30 | return self.hqtfd is not None and self.hqtfd.task_force 31 | 32 | def is_dummy(self) -> bool: 33 | return self.hqtfd is not None and self.hqtfd.dummy 34 | 35 | def is_frame_dashed(self) -> bool: 36 | return (self.affiliation is not None and self.affiliation.is_dashed()) or (self.status is not None and self.status.is_dashed()) or (self.context is not None and self.context.is_dashed()) 37 | 38 | def __repr__(self): 39 | ret = ', '.join([ 40 | 'Symbol', 41 | f'context = {self.context.names[0]}', 42 | f'affiliation = {self.affiliation.names[0] if self.affiliation else 'none'} [{self.affiliation.id_code if self.affiliation else 'none'}]', 43 | f'symbol set = {self.symbol_set.names[0] if self.symbol_set else 'none'} [{self.symbol_set.id_code if self.symbol_set else 'none'}]', 44 | 45 | f'dimension = {self.symbol_set.dimension.names[0] if self.symbol_set and self.symbol_set.dimension else 'none'} [{self.symbol_set.dimension.id_code if self.symbol_set and self.symbol_set.dimension else 'none'}]', 46 | f'frame shape = {self.symbol_set.dimension.frame_shape.names[0] if self.symbol_set else 'none'} [{self.symbol_set.dimension.frame_shape.id_code if self.symbol_set else 'none'}]', 47 | f'entity = {self.entity.names[0]} [{self.entity.id_code}]' if self.entity is not None else 'entity = none', 48 | ] 49 | + ([] if self.frame_shape_override is None else [f'frame shape override = {self.frame_shape_override.names[0]}']) 50 | + ([] if self.modifier_1 is None else [f'm1 = {self.modifier_1.names[0]}']) 51 | + ([] if self.modifier_2 is None else [f'm2 = {self.modifier_2.names[0]}']) 52 | + ([f'status = {self.status.names[0]} [{self.status.id_code}]'] if self.status is not None else []) 53 | + ([f'HQTFD = {self.hqtfd.names[0]} [{self.hqtfd.id_code}]'] if self.hqtfd is not None else []) 54 | + ([f'amplifier = {self.amplifier.names[0]} [{self.amplifier.id_code}]'] if self.amplifier is not None else []) 55 | ) 56 | return ret 57 | 58 | def to_sidc(self) -> str: 59 | return f'13{self.context.id_code if self.context else "0"}{self.affiliation.id_code if self.affiliation else '0'}' + \ 60 | f'{self.status.id_code if self.status else '0'}' + \ 61 | f'{self.hqtfd.id_code if self.hqtfd else '0'}' + \ 62 | f'{self.amplifier.id_code if self.amplifier else '00'}' + \ 63 | f'{self.symbol_set.id_code if self.symbol_set else '00'}' + \ 64 | f'{self.entity.id_code if self.entity else '000000'}' + \ 65 | f'{self.modifier_1.id_code if self.modifier_1 else '00'}' + \ 66 | f'{self.modifier_2.id_code if self.modifier_2 else '00'}' + \ 67 | f'{self.modifier_1.id_code[0] if self.modifier_1 and len(self.modifier_1.id_code) > 2 else '0'}' + \ 68 | f'{self.modifier_2.id_code[0] if self.modifier_2 and len(self.modifier_2.id_code) > 2 else '0'}' + \ 69 | '0000000' 70 | 71 | def get_sidc(self) -> str: 72 | return self.to_sidc() 73 | 74 | def get_name(self) -> str: 75 | return self.__repr__() 76 | 77 | @classmethod 78 | def from_sidc(cls, sidc:str, schema:Schema): 79 | sidc = re.sub(r'\s+', '', sidc) 80 | 81 | if len(sidc) < 20: 82 | raise Exception(f'SIDC "{sidc}" must be at least 20 characters') 83 | if schema is None: 84 | raise Exception('No schema supplied') 85 | 86 | ret = cls() 87 | ret.schema = schema 88 | 89 | # Digits 0,1 are version 90 | 91 | # Digit 2 is the context 92 | ret.context = schema.contexts.get(sidc[2], schema.contexts['0']) 93 | 94 | # Digit 3 is the affiliation 95 | ret.affiliation = schema.affiliations.get(sidc[3], schema.affiliations['0']) 96 | 97 | # Digit 6 is the status 98 | ret.status = schema.statuses.get(sidc[6], schema.statuses['0']) 99 | 100 | # Digit 7 is the HQTFD code 101 | ret.hqtfd = schema.hqtfds.get(sidc[7], schema.hqtfds['0']) 102 | 103 | # Digits 8-9 are the amplifier 104 | ret.amplifier = schema.amplifiers.get(sidc[8:10], schema.amplifiers['00']) 105 | 106 | # Digits 4-5 are the symbol set 107 | ret.symbol_set = schema.symbol_sets.get(sidc[4:6], None) 108 | if ret.symbol_set is None: 109 | print(f'Unknown symbol set \"{sidc[4:6]}\"', file=sys.stderr) 110 | return ret 111 | 112 | # Digits 10-15 are the 113 | entity_code:str = sidc[10:16] 114 | fallbacks:list = [entity_code, f'{entity_code[0:4]}00', f'{entity_code[0:2]}0000'] 115 | for code_index, code in enumerate(fallbacks): 116 | if code in ret.symbol_set.entities: 117 | ret.entity = ret.symbol_set.entities[code] 118 | break 119 | 120 | print(f'Entity "{entity_code}" not found in symbol set "{ret.symbol_set.names[0]}"; falling back to {fallbacks[code_index + 1] if code_index < 2 else '000000'}', file=sys.stderr) 121 | 122 | mod_1_prefix, mod_2_prefix = '', '' 123 | mod_1_set:SymbolSet = ret.symbol_set 124 | mod_2_set:SymbolSet = ret.symbol_set 125 | 126 | if len(sidc) >= 22: 127 | if bool(int(sidc[20])): # Digit 20 indicates the sector 1 modifier is common 128 | mod_1_set = schema.symbol_sets.get('C', mod_1_set) 129 | mod_1_prefix += sidc[20] 130 | if bool(int(sidc[21])): # Digit 21 indicates the sector 2 modifier is common 131 | mod_2_set = schema.symbol_sets.get('C', mod_2_set) 132 | mod_2_prefix += sidc[21] 133 | 134 | if len(sidc) >= 23: 135 | ret.frame_shape_override = schema.frame_shapes.get(sidc[22], None) # Digit 22 is the frame shape override 136 | if ret.frame_shape_override is not None and ret.frame_shape_override.id_code == '0': 137 | ret.frame_shape_override = None 138 | 139 | ret.modifier_1 = mod_1_set.m1.get(mod_1_prefix + sidc[16:18], None) 140 | ret.modifier_2 = mod_2_set.m2.get(mod_2_prefix + sidc[18:20], None) 141 | 142 | return ret 143 | 144 | def get_svg(self, output_style=OutputStyle()) -> str: 145 | if not self.is_valid(): 146 | return None 147 | 148 | # Assemble elements 149 | elements:list = [] 150 | ret_bbox = BBox() 151 | 152 | # Add frame base 153 | frame_to_use = self.frame_shape_override if self.frame_shape_override is not None else self.symbol_set.dimension.frame_shape 154 | 155 | frame_commands = [] 156 | 157 | SVG_NAMESPACE:str = "http://www.w3.org/2000/svg"; 158 | 159 | if self.is_frame_dashed(): 160 | base_frame = frame_to_use.frames[self.affiliation.frame_id] 161 | if output_style.fill_style != 'unfilled': 162 | elements += [base_frame[0].copy_with_stroke(stroke_color='white').svg(symbol=self, output_style=output_style)] 163 | 164 | elements += [base_frame[0].copy_with_stroke(stroke_color='icon', stroke_dashed=True).with_fill(fill_color=None).svg(symbol=self, output_style=output_style)] 165 | 166 | elements += [ 167 | e.svg(symbol=self, output_style=output_style) for e in base_frame[1:] 168 | ] 169 | else: 170 | if output_style.fill_style != 'unfilled': 171 | elements += [e.svg(symbol=self, output_style=output_style) for e in frame_to_use.frames[self.affiliation.frame_id]] 172 | else: 173 | base_frame = frame_to_use.frames[self.affiliation.frame_id] 174 | elements += [base_frame[0].copy_with_fill(fill_color=None).svg(symbol=self, output_style=output_style)] 175 | elements += [e.svg(symbol=self, output_style=output_style) for e in base_frame[1:]] 176 | 177 | frame_commands += [c for c in frame_to_use.frames[self.affiliation.frame_id]] 178 | 179 | frame_bbox = BBox.merge_all([e.get_bbox(symbol=self, output_style=output_style) for e in frame_to_use.frames[self.affiliation.frame_id]]) 180 | ret_bbox.merge(frame_bbox) 181 | 182 | # Handle headquarters 183 | if self.is_headquarters(): 184 | cmd = drawing_items.SymbolElement.Path(d=f"m {ret_bbox.x_min},100 l 0,100", bbox=BBox(ret_bbox.x_min, 100, ret_bbox.x_min, 200)) 185 | frame_commands += [cmd] 186 | elements += [cmd.svg(symbol=self, output_style=output_style)] 187 | ret_bbox.merge(cmd.get_bbox(symbol=self, output_style=output_style)) 188 | 189 | # Add amplfiiers 190 | amplifier_bbox = BBox() 191 | has_amps:bool = self.amplifier is not None and self.amplifier.icon and self.amplifier.icon_side == 'top' 192 | if self.amplifier is not None and self.amplifier.icon: 193 | if self.amplifier.icon_side != 'middle': 194 | amplifier_offset = tuple(frame_to_use.amplifier_offsets[self.affiliation.frame_id][self.amplifier.icon_side]) 195 | 196 | command = drawing_items.SymbolElement.Translate(delta=amplifier_offset, items=self.amplifier.icon) 197 | frame_commands += [command] 198 | elements += [command.svg(symbol=self, output_style=output_style)] 199 | ret_bbox.merge(command.get_bbox(symbol=self, output_style=output_style)) 200 | amplifier_bbox.merge(command.get_bbox(symbol=self, output_style=output_style)) 201 | else: 202 | elements += [e.svg(symbol=self, output_style=output_style) for e in self.amplifier.icon] 203 | frame_commands += [e for e in self.amplifier.icon] 204 | amp_bbox = BBox.merge_all([e.get_bbox(symbol=self, output_style=output_style) for e in self.amplifier.icon]) 205 | ret_bbox.merge(amp_bbox) 206 | amplifier_bbox.merge(amp_bbox) 207 | 208 | if self.is_task_force(): 209 | pass 210 | tf_width = 100 if not has_amps else (amplifier_bbox.width() + 20) 211 | 212 | bounds = BBox( 213 | x_min = 100 - (tf_width / 2), 214 | x_max = 100 + (tf_width / 2), 215 | y_min = amplifier_bbox.y_min - (5 if has_amps else 20), 216 | y_max = frame_bbox.y_min) 217 | 218 | bounds.y_min = bounds.y_min - 5 219 | bounds.y_max = frame_bbox.y_min 220 | 221 | cmd = drawing_items.SymbolElement.Path(d=f"M {bounds.x_min},{bounds.y_max} l 0,-{bounds.height()} l {tf_width},0 l 0,{bounds.height()}", bbox=bounds) 222 | frame_commands += [cmd] 223 | ret_bbox.merge(bounds) 224 | amplifier_bbox.merge(bounds) 225 | elements += [cmd.svg(symbol=self, output_style=output_style)] 226 | 227 | if self.is_dummy(): 228 | half_width = frame_bbox.width() * 0.5 229 | height = round(half_width * math.tan(math.pi / 4), 6) 230 | origin = (frame_bbox.x_min, amplifier_bbox.y_min - 3 if has_amps else frame_bbox.y_min - 3) 231 | cmd = drawing_items.SymbolElement.Path(d=f"M {origin[0]},{origin[1]} l {half_width},-{height} L {frame_bbox.x_max},{origin[1]}", 232 | bbox=BBox(x_min=origin[0], y_min=origin[1] - height, x_max = origin[1] + (2*half_width), y_max = origin[1]), 233 | stroke_dashed=True) 234 | 235 | frame_commands += [cmd] 236 | ret_bbox.merge(cmd.get_bbox(symbol=self, output_style=output_style)) 237 | amplifier_bbox.merge(cmd.get_bbox(symbol=self, output_style=output_style)) 238 | elements += [cmd.svg(symbol=self, output_style=output_style)] 239 | 240 | # Add status 241 | if self.status and self.status.icon: 242 | use_alt:bool = output_style.use_alternate_icons and self.status.alt_icon 243 | icon_to_use = self.status.alt_icon if use_alt else self.status.icon 244 | icon_side = self.status.alt_icon_side if use_alt else self.status.icon_side 245 | 246 | if icon_to_use: 247 | if icon_side != 'middle': 248 | amplifier_offset = tuple(frame_to_use.amplifier_offsets[self.affiliation.frame_id][icon_side]) 249 | command = drawing_items.SymbolElement.Translate(delta=amplifier_offset, items=icon_to_use) 250 | frame_commands += [command] 251 | elements += [command.svg(symbol=self, output_style=output_style)] 252 | ret_bbox.merge(command.get_bbox(symbol=self, output_style=output_style)) 253 | else: 254 | elements += [e.svg(symbol=self, output_style=output_style) for e in icon_to_use] 255 | frame_commands += [e for e in icon_to_use] 256 | status_bbox = BBox.merge_all([e.get_bbox(symbol=self, output_style=output_style) for e in icon_to_use]) 257 | ret_bbox.merge(status_bbox) 258 | 259 | if output_style.background_width > 0.01: 260 | bg_color = output_style.background_color 261 | if not bg_color.startswith('#'): 262 | bg_color = f'#{bg_color}' 263 | 264 | elements = [cmd.copy_with_stroke(stroke_color=f'{bg_color}', stroke_width=output_style.background_width*2, stroke_style='round').svg(symbol=self, output_style=output_style) for cmd in frame_commands] + elements 265 | 266 | # Handle entities and modifiers 267 | for entmod in [self.entity, self.modifier_1, self.modifier_2]: 268 | if not entmod: 269 | continue 270 | icon_to_use = entmod.alt_icon if output_style.use_alternate_icons and entmod.alt_icon else entmod.icon 271 | #print(f'{type(entmod).__name__} {entmod.names[0]}: {entmod.icon}') 272 | elements += [e.svg(symbol=self, output_style=output_style) for e in icon_to_use] 273 | 274 | # Create the SVGs 275 | ret_bbox.expand(padding=output_style.padding) 276 | 277 | if output_style.background_width > 0.01: 278 | ret_bbox.expand(padding=output_style.background_width) 279 | 280 | svg_content = f'\n' + \ 282 | ('\n'.join(elements) if elements is not None else '') + \ 283 | '\n' 284 | 285 | # Determine proper coloration 286 | icon_fill_color = self.schema.affiliations[self.affiliation.color_id].colors.get(output_style.fill_style, OutputStyle.DEFAULT_FILL_STYLE) 287 | 288 | COLOR_DICT = { 289 | 'icon': (0, 0, 0) if output_style.fill_style != 'unfilled' else icon_fill_color, 290 | 'icon_fill': icon_fill_color if output_style.fill_style != 'unfilled' else None, 291 | 'status yellow': (255, 255, 0), 292 | 'status red': (255, 0, 0), 293 | 'status blue': (0, 180, 240), 294 | 'status green': (0, 255, 0), 295 | 'mine yellow': (255, 255, 0), 296 | 'mine orange': (255, 141, 42), 297 | 'mine bright green': (0, 255, 0), 298 | 'mine dark green': (0, 130, 24), 299 | 'mine dark green': (0, 130, 24), 300 | 'mine red': (255, 0, 0), 301 | 'white': (255, 255, 255) if output_style.fill_style != 'unfilled' else None, 302 | 'yellow': (255, 255, 128) 303 | } 304 | 305 | for color_type in ['stroke', 'fill']: 306 | for key, replacement in COLOR_DICT.items(): 307 | svg_content = re.sub(f'{color_type}="{key}"', f'{color_type}="rgb({replacement[0]}, {replacement[1]}, {replacement[2]})"' if replacement is not None else 'none', svg_content) 308 | pass 309 | 310 | return svg_content 311 | 312 | 313 | if __name__ == '__main__': 314 | TEST_SIDCS:list = [ 315 | '130310001411060000600000000000', 316 | '130310000011092000600000000000', 317 | '130560000011020008101100000000', 318 | '130310021316040007891000000000', 319 | '130515003211010300000000000000', 320 | '130310071213010300000000000000', 321 | '130310072313010300000000000000', 322 | '130320400011030007201100000000' 323 | ] 324 | 325 | schema = Schema.load_from_directory() 326 | 327 | test_dir = os.path.join(os.path.dirname(__file__), '..', 'test') 328 | os.makedirs(test_dir, exist_ok=True) 329 | 330 | output_style=OutputStyle() 331 | output_style.use_text_paths = True 332 | output_style.use_alternate_icons = True 333 | 334 | for sidc_raw in TEST_SIDCS: 335 | for affil in ['1', '3', '4', '5', '6']: 336 | sidc = sidc_raw[:3] + affil + sidc_raw[4:] 337 | symbol = Symbol.from_sidc(sidc=sidc, schema=schema) 338 | # print(symbol) 339 | svg = symbol.get_svg(output_style=output_style) 340 | with open(os.path.join(test_dir, f'{sidc}.svg'), 'w') as out_file: 341 | out_file.write(svg) 342 | #print(svg) 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /src/military_symbol/symbol_cache.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append(os.path.dirname(__file__)) 5 | from symbol import Symbol 6 | import name_to_sidc 7 | from schema import Schema 8 | from output_style import OutputStyle 9 | 10 | class SymbolCache: 11 | 12 | STYLE_OPTIONS = [ 13 | 'light', 14 | 'medium', 15 | 'dark', 16 | 'unfilled' 17 | ] 18 | 19 | def __init__(self, symbol_schema): 20 | self.sidc_to_symbol_map:dict = {} # Map of SIDCs + options to SVG string 21 | self.name_to_sidc_string_map:dict = {} # Map of names to (symbol, SIDC) pairs 22 | self.symbol_schema = symbol_schema 23 | 24 | @classmethod 25 | def options_string_encode(cls, padding:int, style:str, use_variants:bool, use_background:bool, background_color:str): 26 | """ 27 | Encodes options into a three-character string for string caching, with character 1 indicating padding as 28 | an int (X for values less than 0), one-character indicating style from options ['light', 'medium', 'dark', 29 | unfilled'], and a 0/1 Boolean value indicating whether to use variant symbols if available 30 | :param padding: An integer for padding SVGs 31 | :param style: String indicating style from ['light', 'medium', 'dark', 'unfilled'] 32 | :param use_variants: Boolean indicating whether to use variant symbols if they exist 33 | :param use_background: Boolean indicating whether to use the background "halo" 34 | :param background_color: Background color 35 | :return: Three-character string of encoded options 36 | """ 37 | return '{}{}{}{}{}'.format( 38 | 'X' if padding < 0 else padding, 39 | style[0].lower(), 40 | '1' if use_variants else '0', 41 | '1' if use_background else '0', 42 | background_color 43 | ) 44 | 45 | def _get_symbol_cache_from_sidc(self, sidc, create_if_missing:bool=True, verbose:bool=False) -> tuple: 46 | symbol_cache_entry:tuple = self.sidc_to_symbol_map.get(sidc, ()) 47 | if len(symbol_cache_entry) < 1: 48 | # Entry doesn't exist 49 | if create_if_missing: 50 | # Tuple doesn't exist 51 | symbol:Symbol = Symbol.from_sidc(sidc=sidc, schema=self.symbol_schema) 52 | symbol_cache_entry = (symbol, {}) 53 | self.sidc_to_symbol_map[sidc] = symbol_cache_entry 54 | 55 | return symbol_cache_entry 56 | else: 57 | return None 58 | else: 59 | # print('Symbol cache hit') 60 | return symbol_cache_entry 61 | 62 | def get_symbol_from_sidc(self, sidc:str, create_if_missing:bool=True, verbose:bool=False) -> Symbol: 63 | ret = self._get_symbol_cache_from_sidc(sidc, create_if_missing, verbose=verbose) 64 | return ret[0] if ret is not None else None 65 | 66 | def _get_symbol_cache_from_name(self, name, create_if_missing:bool=True, verbose:bool=False, limit_to_symbol_sets=None) -> tuple: 67 | sidc:str = self.name_to_sidc_string_map.get(name, '') 68 | if sidc == '': 69 | if create_if_missing: 70 | symbol:Symbol = name_to_sidc.name_to_symbol(name, self.symbol_schema, verbose=verbose, limit_to_symbol_sets=limit_to_symbol_sets) 71 | sidc = symbol.to_sidc() 72 | cache_entry:tuple = (symbol, {}) 73 | self.sidc_to_symbol_map[sidc] = cache_entry 74 | self.name_to_sidc_string_map[name] = sidc 75 | return cache_entry 76 | else: 77 | return None 78 | else: 79 | # print('Cache hit on symbol cache to name') 80 | return self.sidc_to_symbol_map[sidc] 81 | 82 | def get_symbol_from_name(self, name, create_if_missing:bool=True, verbose:bool=False, limit_to_symbol_sets=None) -> Symbol: 83 | cache_entry:tuple = self._get_symbol_cache_from_name(name, create_if_missing, verbose=verbose, limit_to_symbol_sets=limit_to_symbol_sets) 84 | return cache_entry[0] if cache_entry is not None else None 85 | 86 | def get_symbol(self, creator_var:str, is_sidc:bool, create_if_missing:bool=True, verbose:bool=False, limit_to_symbol_sets=None): 87 | if is_sidc: 88 | return self.get_symbol_from_sidc(creator_var, create_if_missing, verbose=verbose) 89 | else: 90 | return self.get_symbol_from_name(creator_var, create_if_missing, verbose=verbose, limit_to_symbol_sets=limit_to_symbol_sets) 91 | 92 | def get_symbol_and_svg_string(self, creator_val:str, is_sidc:bool, padding:int, style:str, use_variants:bool, 93 | use_background:bool=True, background_color:str='#ffffff', create_if_missing:bool=True, verbose:bool=False, force_all_elements:bool=False, 94 | limit_to_symbol_sets=None) -> tuple: 95 | 96 | cache_entry_tuple: tuple = self._get_symbol_cache_from_sidc(creator_val, 97 | create_if_missing) if is_sidc else self._get_symbol_cache_from_name( 98 | creator_val, create_if_missing, verbose=verbose, limit_to_symbol_sets=limit_to_symbol_sets) 99 | 100 | if cache_entry_tuple is None: 101 | return None, '' 102 | 103 | symbol: Symbol = cache_entry_tuple[0] 104 | svg_map: dict = cache_entry_tuple[1] 105 | 106 | key_string: str = self.options_string_encode(padding, style, use_variants, use_background, background_color) 107 | svg_string: str = svg_map.get(key_string, '') 108 | if svg_string == '': 109 | if create_if_missing: 110 | output_style = OutputStyle() 111 | output_style.padding = padding 112 | output_style.use_alternate_icons = use_variants 113 | output_style.use_background = use_background 114 | output_style.background_width = OutputStyle.DEFAULT_BACKGROUND_WIDTH if use_background else 0 115 | output_style.background_color = background_color 116 | output_style.fill_style = style 117 | 118 | svg_string = symbol.get_svg(output_style=output_style) 119 | svg_map[key_string] = svg_string 120 | return symbol, svg_string 121 | else: 122 | return symbol, '' 123 | else: 124 | return symbol, svg_string 125 | 126 | def get_svg_string(self, creator_val:str, is_sidc:bool, padding:int, style:str, use_variants:bool, use_background:bool=True, 127 | background_color:str='#ffffff', create_if_missing:bool=True, verbose:bool=False, force_all_elements:bool=False, 128 | limit_to_symbol_sets=None): 129 | 130 | return self.get_symbol_and_svg_string(creator_val, is_sidc, padding, style, use_variants, use_background, 131 | background_color, create_if_missing, verbose=verbose, force_all_elements=force_all_elements)[1] 132 | 133 | def get_svg_string_from_name(self, name, padding:int, style:str, use_variants:bool=False, use_background:bool=True, 134 | background_color:str='#ffffff', create_if_missing:bool=True, verbose:bool=False, force_all_elements:bool=False, 135 | limit_to_symbol_sets=None): 136 | 137 | return self.get_svg_string(name, False, padding, style, use_variants, use_background, background_color, create_if_missing, 138 | verbose=verbose, force_all_elements=force_all_elements) 139 | 140 | def get_svg_string_from_sidc(self, sidc, padding:int, style:str, use_variants:bool=False, use_background:bool=True, 141 | background_color:str='#ffffff', create_if_missing:bool=True, verbose:bool=False, force_all_elements:bool=False): 142 | 143 | return self.get_svg_string(sidc, True, padding, style, use_variants, use_background, background_color, create_if_missing, 144 | verbose=verbose, force_all_elements=force_all_elements) -------------------------------------------------------------------------------- /src/military_symbol/template.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import json 4 | import sys 5 | import yaml 6 | import copy 7 | 8 | sys.path.append(os.path.dirname(__file__)) 9 | from schema import Schema 10 | from symbol import Symbol 11 | import name_to_sidc 12 | 13 | class Template(): 14 | """ 15 | A template for a symbol, indicating a flexible alias for a symbol 16 | """ 17 | 18 | ITEMS:list = { 19 | 'context': (2, 1, 'contexts'), # Start, length 20 | 'affiliation': (3, 1, 'affiliations'), 21 | 'symbol_set': (4, 2, 'symbol_sets'), 22 | 'status': (6, 1, 'statuses'), 23 | 'hqtfd': (7, 1, 'hqtfds'), 24 | 'amplifier': (8, 2, 'amplifiers'), 25 | 26 | 'entity': (10, 6, 'entities'), 27 | 'modifier_1': (16, 2, 'm1'), 28 | 'modifier_2': (18, 2, 'm2') 29 | } 30 | 31 | SYMBOL_SET_ITEMS:list = [ 32 | 'entity', 'modifier_1', 'modifier_2' 33 | ] 34 | 35 | def __init__(self): 36 | self.names:list = [] # A list of names for the template 37 | self.sidc:str = '' # The original SIDC for the template 38 | 39 | for item in Template.ITEMS: 40 | setattr(self, f'{item}_is_flexible', True) 41 | 42 | def get_names(self) -> list: 43 | return self.names 44 | 45 | @classmethod 46 | def load_from_sidc(cls, sidc:str, schema:Schema, names:list = [], verbose:bool=False): 47 | sidc = re.sub(r'\s+', '', sidc) 48 | if len(sidc) < 2: 49 | raise Exception(f'Template SIDC must be at least 20 characters (spaces don\'t count), but is "{sidc}"') 50 | 51 | if verbose: 52 | print(f'Loading {names} from SIDC "{sidc}') 53 | 54 | symbol_sidc:str = re.sub(r'[\*_]', '0', sidc) 55 | 56 | ret = cls() 57 | ret.sidc = sidc 58 | 59 | try: 60 | ret.symbol = Symbol.from_sidc(sidc=symbol_sidc, schema=schema) 61 | except ex: 62 | raise Exception(f'Error loading template from SIDC {sidc}: {ex}') 63 | 64 | for item_name, (start, length, attr) in Template.ITEMS.items(): 65 | entry = sidc[start:(start + length)] 66 | if re.findall(r'[\*_]', entry): 67 | setattr(ret, f'{item_name}_is_flexible', True) 68 | else: 69 | setattr(ret, f'{item_name}_is_flexible', False) 70 | 71 | ret.names = copy.copy(names) 72 | return ret 73 | 74 | def __repr__(self) -> str: 75 | return f'{'/'.join([f'"{name}"' for name in self.names])}{f' [{self.sidc}]' if self.sidc else ''}{self.symbol}' 76 | 77 | @classmethod 78 | def load_from_dict(cls, data:dict, schema:Schema, names:list = [], verbose:bool=False): 79 | names = names + data.get('names', []) 80 | if 'sidc' in data: 81 | #raise Exception(f'Template file "{filename}" item "{name}" doesn\'t have a "sidc" entry') 82 | sidc = data.get('sidc', '') 83 | try: 84 | template = Template.load_from_sidc(sidc=sidc, names=names, schema=schema, verbose=verbose) 85 | except Exception as ex: 86 | print(f'Error loading template from SIDC "{sidc}"; skipping: {ex}', file=sys.stderr) 87 | return None 88 | 89 | if template is not None: 90 | return template 91 | 92 | if verbose: 93 | print(f'Loading template {names} from dictionary') 94 | 95 | temp:Template = Template() 96 | temp.names = names 97 | temp.symbol = Symbol(schema=schema) 98 | for item_name, (start, length, attr) in Template.ITEMS.items(): 99 | if item_name not in data: 100 | if verbose: 101 | print(f'\t{item_name} is flexible') 102 | 103 | setattr(temp.symbol, f'{item_name}_is_flexible', True) 104 | continue 105 | 106 | item_code:str = re.sub(r'[\s\n]+', '', str(data[item_name])) 107 | if len(item_code) < length: 108 | item_code = item_code.rjust(length, '0') 109 | 110 | if len(item_code) != length and not item_name.startswith('modifier'): 111 | print(f'Item \"{item_name}\" for a template must be of length {length}; ignoring template item from file "{filename}"', file=sys.stderr) 112 | return None 113 | 114 | if item_name not in Template.SYMBOL_SET_ITEMS: 115 | choices:dict = getattr(schema, attr) 116 | if item_code not in choices: 117 | print(f'\"{item_code}\" is not a valid value for {item_name} in "{filename}"; skipping', file=sys.stderr) 118 | return None 119 | 120 | setattr(temp, f'{item_name}_is_flexible', False) 121 | setattr(temp.symbol, item_name, choices[item_code]) 122 | elif temp.symbol.symbol_set is not None: 123 | # Symbol set 124 | 125 | choices:dict = getattr(temp.symbol.symbol_set, attr) 126 | if item_name in ['modifier_1', 'modifier_2'] and len(item_code) == 3: 127 | choices = getattr(schema.symbol_sets['C'], attr) 128 | 129 | if item_code not in choices: 130 | print(f'\"{item_code}\" is not a valid value for symbol set {temp.symbol.symbol_set.id_code} {item_name}; skipping', file=sys.stderr) 131 | return None 132 | 133 | setattr(temp.symbol, item_name, choices[item_code]) 134 | setattr(temp, f'{item_name}_is_flexible', False) 135 | 136 | 137 | if temp.symbol.context is None: 138 | temp.symbol.context = schema.contexts['0'] 139 | if temp.symbol.affiliation is None: 140 | temp.symbol.affiliation = schema.affiliations['0'] 141 | 142 | # print(f'Loaded template {temp}') 143 | return temp 144 | 145 | @classmethod 146 | def load_from_file(cls, filename:str, schema=None, verbose:bool=False) -> list: 147 | if not schema: 148 | schema = Schema.load() 149 | if not os.path.exists(filename): 150 | raise Exception(f'Template file \"{filename}\" doesn\'t exist') 151 | with open(filename, 'r') as in_file: 152 | data = yaml.safe_load(in_file) 153 | 154 | ret = [] 155 | for name, data in data.items(): 156 | template = Template.load_from_dict(names=[name], schema=schema, data=data, verbose=verbose) 157 | if template is not None: 158 | ret.append(template) 159 | 160 | return ret 161 | 162 | 163 | --------------------------------------------------------------------------------