```{eval-rst} .. meta:: :description: An introduction to the CloudRF API for automating radio propagation modelling, including its use cases. ``` # API The CloudRF API offers a powerful and scalable service to model diverse radio networks anywhere in the world at high resolution. When integrated into another system, it can automate modelling to save significant development effort with a modest budget. The rapid nature of its processing enables live processing for dynamic systems such as spectrum management or autonomous vehicles. **Use cases** - Automate customer qualification based upon a postal address - Robot route selection - Best site analysis for optimising deployments - Receiver modelling for signal multi-lateration - Generation of network coverage maps for marketing or briefings - Automated regression testing for network changes - Integration with cognitive radios for dynamic reconfiguratio ## OpenAPI Schema For a complete OpenAPI version 3 schema of the CloudRF API please consult the [OpenAPI documentation](https://cloudrf.com/documentation/developer/). ## API Endpoint For CloudRF users, the API endpoint is `https://api.cloudrf.com`. Users with a private SOOTHSAYER server will have an IP address or custom domain name instead. The API endpoints and parameters are the same and are protected with TLS encryption. ## Authentication Each user has a unique private API key which resembles a long random string of characters. You should protect this key to prevent unauthorised use of your account. An example of an API key is as below: ``` 101-ec94622a4cb939a77101a118c6871d03cea88af3 ``` ### Using your API Key The CloudRF API requires API key authentication in the request header. This provides a key-value pair with `key` as the first value and your personal key as the second, paired value. For example, [Postman](https://learning.postman.com/docs/sending-requests/authorization/#api-key) allows you to enter your key from their interface. ### Key security It is important to keep your API key secure. Publicly exposing your key can compromise your account, which could result in a loss of data or unexpected charges. A key does not give access to your interface or account management which is separate. To keep your API keys secure, follow some best practices: #### Keys as variables Store API keys as environment variables. This has an added benefit of accessing your key via a friendly environment variable name rather than having to remember your full key each time. **Do not embed API keys directly in code as they will be exposed eventually.** #### Keys as private files You can store your API key in a private file and reference that file each time you need to use your API key. **If you store API keys in files, store those files outside your application's source tree.** Doing so helps to ensure that your keys do not end up in your source code version control system. This is particularly important if you use a public source code management system such as GitHub. ## "Hello World!" example Send this curl request to model coverage for a UHF radio, 2m above the ground with 2W of power: ```bash curl --location 'https://api.cloudrf.com/area' \ --header 'key: YOUR-API-KEY-HERE' \ --data '{ "site": "HelloWorld", "network": "Testing", "transmitter": { "lat": 38.916, "lon": 1.448, "alt": 2, "frq": 450, "txw": 2, "bwi": 0.012 }, "receiver": { "lat": 0, "lon": 0, "alt": 2, "rxg": 0, "rxs": -105 }, "antenna": { "txg": 0, "txl": 0, "ant": 1, "azi": 0 }, "environment": { "clt": "Temperate.clt", "landcover": 1, "buildings": 1, "terrain": 2 }, "output": { "units": "metric", "col": "LTE.dBm", "out": 2, "res": 10, "rad": 5 } }' ``` ## Example API Requests Below lists some basic examples of some of the most common types of requests made to the CloudRF API. For a complete list of available schemas please consult the [OpenAPI documentation](https://cloudrf.com/documentation/developer/). With all requests some values are required and so the response will return any validation errors or any failures should your request not be able to be processed. ### Area The `area` endpoint accepts a JSON object in the request **body** describing your network and will run a point-to-multipoint "heatmap" calculation. #### Request The below example is for an omni-directional antenna on an 8m mast at 446MHz. The request is sent as a `POST` request to `https://api.cloudrf.com/area` and it will return a JSON response containing metadata and URLs to image layers for your map. ```json { "site": "Harbour", "network": "PMR", "engine": "2", "transmitter": { "lat": "38.913767", "lon": "1.440017", "alt": "8", "frq": "446", "txw": "1", "bwi": "0.1" }, "receiver": { "lat": 0, "lon": 0, "alt": "2", "rxg": "2", "rxs": "-90" }, "antenna": { "txg": "2.15", "txl": "0", "ant": "1", "azi": "0", "tlt": "0", "hbw": "0", "vbw": "0", "fbr": "0", "pol": "v" }, "model": { "pm": "1", "pe": "2", "ked": "1", "rel": "50" }, "environment": { "clt": "Minimal.clt", "elevation": "2", "landcover": "1", "buildings": "0", "obstacles": "0" }, "output": { "units": "m", "col": "RAINBOW45.dBm", "out": "2", "nf": "-124", "res": "20", "rad": "8" } } ``` #### Response The `PNG_Mercator` image is warped for slippy maps like Mapbox, Leaflet and Google Maps. The `PNG_WGS84` is for globes like Google Earth, Cesium and WinTAK. ```json { "kmz": "https://api.cloudrf.com/archive/eFYyWFpUYW0zR1pxcTIyRlRKREQxUT09/kmz", "PNG_Mercator": "https://api.cloudrf.com/users/1/0812210016_PMR_harbour.3857.png", "PNG_WGS84": "https://api.cloudrf.com/users/1/0812210016_PMR_harbour.4326.png", "bounds": [ 39.00613, 1.532378, 38.82141, 1.347656 ], "id": 8191195, "sid": "eFYyWFpUYW0zR1pxcTIyRlRKREQxUT09", "area": 55.9, "coverage": 28.0, "key": [ { "l": "-45dBm", "r": 37, "g": 131, "b": 255 }, { "l": "-55dBm", "r": 46, "g": 254, "b": 187 }, { "l": "-65dBm", "r": 98, "g": 254, "b": 55 }, { "l": "-75dBm", "r": 254, "g": 234, "b": 63 }, { "l": "-85dBm", "r": 254, "g": 72, "b": 72 } ], "elapsed": 2297.0, "balance": 23476 } ``` PNG image with RGB colours which map to the signal levels in the JSON output: ![Area demo](Images/API/area_demo.png) #### Go Faster With The GPU Engine Switch the engine parameter to `1` to use the GPU engine. All other settings are the same. ```json { "site": "Harbour", "network": "PMR", "engine": "1", ... snipped for brevity ... } ``` Please note that to be able to make use of the GPU engine you require an active GPU subscription or SOOTHSAYER with GPU functionality enabled. #### Multi-Azimuth Requests You can specify an array of azimuths to model panels on a cell tower for custom patterns and templates. Pass azimuths in as a comma separated list in quotes like `"0,90,180,270"`. The maximum number of azimuths you can pass in one API request is 90. This works for CPU and GPU engines. ```json { "site": "FourPanels", "network": "800MHZ", "engine": "2", "transmitter": { "lat": "38.881037", "lon": "1.468", "alt": "12", "frq": "800", "txw": "1", "bwi": "0.1" }, "receiver": { "lat": 0, "lon": 0, "alt": "2", "rxg": "2", "rxs": "-90" }, "antenna": { "txg": "21", "txl": "0", "ant": 0, "azi": "0,90,180,270", "tlt": "1", "hbw": "80", "vbw": "80", "fbr": "21", "pol": "v" }, "model": { "pm": "11", "pe": "2", "ked": "1", "rel": "50" }, "environment": { "clt": "Minimal.clt", "elevation": "2", "landcover": "0", "buildings": "0", "obstacles": "0" }, "output": { "units": "m", "col": "RAINBOW45.dBm", "out": "2", "nf": "-120", "res": "20", "rad": "5" } } ``` ![Antennas demo](Images/API/antennas_demo.jpg) ### Path The `path` endpoint accepts a JSON object describing your network and will run a point-to-point calculation. #### Request The following example includes some optional parameters and provides the required data to model a link with land cover and 3D buildings enabled at 30m resolution. ***The request is similar to the `area` call except the receiver latitude (`lat`) and longitude (`lon`) values are populated.*** The below example is sent as a `POST` request to `https://api.cloudrf.com/path`. ```json { "site": "HarbourLink", "network": "LPWAN", "transmitter": { "lat": "38.9090", "lon": "1.44094", "alt": 12, "frq": "868", "txw": "0.1", "bwi": "0.1" }, "receiver": { "lat": "38.9173881", "lon": "1.46864923", "alt": 12, "rxg": "2", "rxs": "-90" }, "antenna": { "txg": "2.15", "txl": "0", "ant": "1", "azi": "0", "tlt": "0", "hbw": "1", "vbw": "1", "pol": "v" }, "model": { "pm": "1", "pe": "2", "ked": "1", "rel": "95" }, "environment": { "clt": "Minimal.clt", "elevation": "2", "landcover": "1", "buildings": "1", "obstacles": "0" }, "output": { "units": "m", "col": "RAINBOW45.dBm", "out": "2", "res": "30", "rad": "2" } } ``` #### Response The response contains metadata about the link along with raw values necessary to build a chart using a graphing library such as Matplotlib or Plotly. It also contains a link to a KMZ so you can see the profile in 3D. ```json { "Engine": "Sleipnir 1.7.8", "Frequency MHz": 868, "Propagation model": "ITM", "Earth dielectric constant": 13, "Earth conductivity": 0.002, "Radio climate": "Maritime Temperate (Land)", "Atmospheric bending constant": 301, "Fraction of situations": 95, "Fraction of time": 95, "Receiver": [ { "Latitude": 38.91739, "Longitude": 1.468649, "Ground elevation m": 28, "Antenna height m": 12, "Receiver gain dBd": -0.15, "Receiver gain dBi": 2 } ], "Transmitters": [ { "Latitude": 38.90908, "Longitude": 1.440943, "Ground elevation m": 1, "Antenna height m": 12, "Distance to receiver km": 2.572, "Azimuth to receiver deg": 68.92, "Downtilt angle deg": 0.6, "Antenna gain dBd": 0, "Antenna gain dBi": 2.15, "Polarisation": "Vertical", "Power W": 0.1, "Power dBm": 22.15, "ERP W": 0.1, "EIRP W": 0.164, "ERP dBm": 20, "EIRP dBm": 22.15, "Free space path loss dB": 95.3, "Bandwidth MHz": 0.1, "Johnson Nyquist noise dB": 0.2, "Noise floor dBm": -120, "Channel noise dBm": -119.8, "Signal power at receiver dBm": -88.6, "Signal to Noise Ratio dB": 31.2, "Computed path loss dB": 108.6, "Model attenuation dB": 13.3, "Field strength at receiver dBuV/m": 49.6, "RX voltage 50 ohm dipole uV": 14, "RX voltage 50 ohm dipole dBuV": 23, "RX voltage 75 ohm dipole uV": 17, "RX voltage 75 ohm dipole dBuV": 24, "Raise RX antenna for LOS": 0, "Raise RX antenna for fresnel 60%": 0, "Raise RX antenna for full fresnel": 19, "Obstructions": [], "Distance": [ 0.03, 0.059, 0.089, 0.119, 0.148, ... snipped for brevity ... ], "Terrain": [ 1, 1, 1, 1, 1, ... snipped for brevity ... ], "Terrain_AMSL": [ 1, 0, 0, 0, 0, ... snipped for brevity ... ], "Landcover distance": [ 0.03, 0.059, 0.089, 0.119, 0.148, ... snipped for brevity ... ], "Landcover codes": [ 1, 1, 1, 1, 1, ... snipped for brevity ... ], "Landcover heights": [ 1, 1, 1, 1, 1, ... snipped for brevity ... ], "Fresnel": [ 0, -3.18, -4.47, -5.45, -6.25, ... snipped for brevity ... ], "dBm": [ -50, -56, -60, -62, -64, ... snipped for brevity ... ], "dB": [ 70, 76, 80, 82, 84, ... snipped for brevity ... ] } ], "calculation_adjusted": [], "elapsed": 130.0, "Chart image": "https://api.cloudrf.com/API/archive/data?sid=c2ZrYXdUa0tPeGFrbG5wS1dLd3RIdz09&type=ppa", "kmz": "https://api.cloudrf.com/API/archive/data?sid=c2ZrYXdUa0tPeGFrbG5wS1dLd3RIdz09&type=path" } ``` ![Path demo](Images/API/pathprofile_chart.jpg) The landcover code meanings are as follows: - `1` Water - `2` Trees - `3` Grassland - `4` Swamp - `5` Crops - `6` Shrubland - `7` Built-up - `8` Bare ground - `9` Snow / Ice - `10` Buildings - `11` Custom Clutter 1 - `12` Custom Clutter 2 - `13` Custom Clutter 3 - `14` Custom Clutter 4 - `15` Custom Clutter 5 - `16` Custom Clutter 6 - `17` Custom Clutter 7 - `18` Custom Clutter 8 - `19` Custom Clutter 9 ### Points The `points` endpoint accepts a JSON object describing an array of points (transmitters) which will be tested back to a single point (receiver). #### Request The following example is for route a boat will take. The location varies with each point but all the other values are constant. The request is sent as a `POST` body to `https://api.cloudrf.com/points`. ```json { "site": "RIB", "network": "VHF", "transmitter": { "lat": 38.914381, "lon": 1.436988, "alt": "2", "frq": "160", "txw": "1", "bwi": "0.1" }, "points": [ { "lat": 38.91086406303705, "lon": 1.444486542453175, "alt": 2 }, { "lat": 38.91041372754393, "lon": 1.444495874752767, "alt": 2 }, { "lat": 38.90996339201471, "lon": 1.4445052069344337, "alt": 2 }, { "lat": 38.909513056449505, "lon": 1.444514538998178, "alt": 2 } ], "receiver": { "lat": 38.914381, "lon": 1.436988, "alt": 2, "rxg": "2", "rxs": "-90" }, "antenna": { "txg": "2.15", "txl": "0", "ant": "1", "azi": "0", "tlt": "0", "hbw": "1", "vbw": "1", "fbr": "2.15", "pol": "v" }, "model": { "pm": "11", "pe": "2", "ked": "1", "rel": "50" }, "environment": { "elevation": "2", "landcover": "1", "buildings": "0", "obstacles": "0" }, "output": { "units": "m", "col": "RAINBOW45.dBm", "out": "2", "nf": "-124", "res": "30", "rad": "8" } } ``` #### Response The response contains JSON metadata for the points, in this case received power levels for each point. ```json { "Engine": "Sleipnir 1.7.8", "Frequency MHz": 160, "Propagation model": "Egli VHF/UHF", "Model subtype": "Suburban", "Receiver": [ { "Latitude": 38.91438, "Longitude": 1.436988, "Ground elevation m": 1, "Antenna height m": 2, "Receiver gain dBd": -0.15, "Receiver gain dBi": 2 } ], "Transmitters": [ { "Latitude": 38.91086, "Longitude": 1.444487, "Ground elevation m": 0, "Antenna height m": 2, "Distance to receiver km": 0.758, "Azimuth to receiver deg": 301.08, "Downtilt angle deg": 0.1, "Antenna gain dBd": 0, "Antenna gain dBi": 2.15, "Polarisation": "Vertical", "Power W": 1, "Power dBm": 2.15, "ERP W": 0.001, "EIRP W": 1.641, "ERP dBm": 0, "EIRP dBm": 2.15, "Free space path loss dB": 70, "Bandwidth MHz": 0.1, "Johnson Nyquist noise dB": 0.2, "Noise floor dBm": -124, "Channel noise dBm": -123.8, "Signal power at receiver dBm": -66.2, "Signal to Noise Ratio dB": 57.6, "Computed path loss dB": 96.2, "Model attenuation dB": 26.2, "Field strength at receiver dBuV/m": 27.3, "Raise RX antenna for LOS": 5, "Raise RX antenna for fresnel 60%": 77, "Raise RX antenna for full fresnel": 128, "server": 1 }, { "Latitude": 38.91041, "Longitude": 1.444496, "Ground elevation m": 0, "Antenna height m": 2, "Distance to receiver km": 0.786, "Azimuth to receiver deg": 304.18, "Downtilt angle deg": 0.1, "Antenna gain dBd": 0, "Antenna gain dBi": 2.15, "Polarisation": "Vertical", "Power W": 1, "Power dBm": 2.15, "ERP W": 0.001, "EIRP W": 1.641, "ERP dBm": 0, "EIRP dBm": 2.15, "Free space path loss dB": 70.3, "Bandwidth MHz": 0.1, "Johnson Nyquist noise dB": 0.2, "Noise floor dBm": -124, "Channel noise dBm": -123.8, "Signal power at receiver dBm": -66.8, "Signal to Noise Ratio dB": 57, "Computed path loss dB": 96.8, "Model attenuation dB": 26.5, "Field strength at receiver dBuV/m": 26.7, "Raise RX antenna for LOS": 5, "Raise RX antenna for fresnel 60%": 80, "Raise RX antenna for full fresnel": 132, "server": 2 }, { "Latitude": 38.90996, "Longitude": 1.444505, "Ground elevation m": 0, "Antenna height m": 2, "Distance to receiver km": 0.816, "Azimuth to receiver deg": 307.06, "Downtilt angle deg": 0.1, "Antenna gain dBd": 0, "Antenna gain dBi": 2.15, "Polarisation": "Vertical", "Power W": 1, "Power dBm": 2.15, "ERP W": 0.001, "EIRP W": 1.641, "ERP dBm": 0, "EIRP dBm": 2.15, "Free space path loss dB": 70.6, "Bandwidth MHz": 0.1, "Johnson Nyquist noise dB": 0.2, "Noise floor dBm": -124, "Channel noise dBm": -123.8, "Signal power at receiver dBm": -67.5, "Signal to Noise Ratio dB": 56.3, "Computed path loss dB": 97.5, "Model attenuation dB": 26.9, "Field strength at receiver dBuV/m": 26, "Raise RX antenna for LOS": 4, "Raise RX antenna for fresnel 60%": 82, "Raise RX antenna for full fresnel": 136, "server": 3 }, { "Latitude": 38.90951, "Longitude": 1.444515, "Ground elevation m": 0, "Antenna height m": 2, "Distance to receiver km": 0.848, "Azimuth to receiver deg": 309.74, "Downtilt angle deg": 0.1, "Antenna gain dBd": 0, "Antenna gain dBi": 2.15, "Polarisation": "Vertical", "Power W": 1, "Power dBm": 2.15, "ERP W": 0.001, "EIRP W": 1.641, "ERP dBm": 0, "EIRP dBm": 2.15, "Free space path loss dB": 70.9, "Bandwidth MHz": 0.1, "Johnson Nyquist noise dB": 0.2, "Noise floor dBm": -124, "Channel noise dBm": -123.8, "Signal power at receiver dBm": -68.1, "Signal to Noise Ratio dB": 55.7, "Computed path loss dB": 98.1, "Model attenuation dB": 27.2, "Field strength at receiver dBuV/m": 25.3, "Raise RX antenna for LOS": 4, "Raise RX antenna for fresnel 60%": 85, "Raise RX antenna for full fresnel": 141, "server": 4 } ], "calculation_adjusted": [], "elapsed": 111.0, "kmz": "https://api.cloudrf.com/API/archive/data?points=0812220045_mymesh_PPA_POINTS&uid=1", "json": "https://api.cloudrf.com/users/1/0812220045_mymesh_PPA_POINTS.json" } ``` ![Points demo](Images/API/route_demo.jpg) ### Best Site Analysis The `bsa` endpoint accepts a JSON object in the request **body** describing a location with a radius, similar to an 'area' call except it uses a random Monte-Carlo technique to rank locations within the area to identify the best. The colour key is irrelevant for the API since the response is always a greyscale image which you must style. #### Request The below example is for a planned Sub GHz LPWAN gateway in the hills. The request is sent as a `POST` request to `https://api.cloudrf.com/bsa` and it will return a JSON response containing metadata and URLs to image layers for your map. ```json { "site": "BestSite", "network": "Ibiza", "engine": "1", "transmitter": { "lat": 38.938092, "lon": 1.389430, "alt": "2", "frq": "446", "txw": "1", "bwi": "0.1" }, "receiver": { "lat": 0, "lon": 0, "alt": "2", "rxg": "2", "rxs": "-90" }, "antenna": { "txg": "2.15", "txl": "0", "ant": "1", "azi": "0", "tlt": "0", "hbw": "0", "vbw": "0", "fbr": "2.15", "pol": "v" }, "model": { "pm": "7", "pe": "2", "ked": "0", "rel": "95" }, "environment": { "clt": "Minimal.clt", "elevation": "2", "landcover": "0", "buildings": "0", "obstacles": "0" }, "output": { "units": "m", "col": "BSA8.bsa", "out": "7", "nf": "-120", "res": "10", "rad": 3 } } ``` #### Response The `PNG_Mercator` image is warped for slippy maps like Mapbox, Leaflet and Google Maps. The `PNG_WGS84` is for globes like Google Earth, Cesium and WinTAK. A BSA response is almost identical to an area response. The real difference is in the image which is always greyscale where white is 100% efficiency and black is 0% efficiency. The colour key returned can be used for styling by isolating only the red channel since the green and blue channels will be the same eg. RGB(254,254,254) = 95% coverage. The area and coverage fields are zero since this is not a site. ```json { "kmz": "https://api.cloudrf.com/archive/a2s1bWJkTDJYdGMrNUNHZVhpTUtUQT09/kmz", "PNG_Mercator": "https://api.cloudrf.com/output/projection/3857/1/0816125807_IBIZA_BestSite", "PNG_WGS84": "https://api.cloudrf.com/users/1/0816125807_IBIZA_BestSite.4326.png", "bounds": [ 38.96563941396777, 1.4248893862148355, 38.91054585887422, 1.3539723152977645 ], "id": 8201477, "sid": "a2s1bWJkTDJYdGMrNUNHZVhpTUtUQT09", "area": 0, "coverage": 0, "key": [ { "l": "95%", "r": 254, "g": 0, "b": 0 }, { "l": "90%", "r": 254, "g": 91, "b": 0 }, { "l": "85%", "r": 254, "g": 183, "b": 0 }, { "l": "80%", "r": 236, "g": 254, "b": 0 }, { "l": "75%", "r": 145, "g": 254, "b": 0 }, { "l": "70%", "r": 53, "g": 254, "b": 0 }, { "l": "65%", "r": 0, "g": 254, "b": 38 }, { "l": "60%", "r": 0, "g": 254, "b": 129 }, { "l": "55%", "r": 0, "g": 254, "b": 221 }, { "l": "50%", "r": 0, "g": 198, "b": 255 } ], "elapsed": 1147.0, "balance": 23305 } ``` A BSA response is shown below. Notice that ridgelines rank high, but not as high as the low ground to the east which has the best visibility of the entire area: ![BSA demo](Images/API/bsa_demo.jpg) #### Request With A Polygon If you want to use a polygon shape to define boundaries, you can use the `edges` array to define each point. A minimum of 3 points are needed as `lat`/`lon` pairs within the `edges` array. With this you can draw political or property boundaries. A radius is automatically computed using this method so it doesn't matter what radius you request. ```json { "edges": [ { "lat": 38.94, "lon": 1.39 }, { "lat": 38.94, "lon": 1.399 }, { "lat": 38.945, "lon": 1.395 } ], "site": "Triangle", "network": "Ibiza", "engine": "1", "transmitter": { "lat": 38.945, "lon": 1.392, "alt": "2", "frq": "446", "txw": "1", "bwi": "0.1" }, ... snipped for brevity ... } ``` By defining 3 points, we have reduced the study area to a triangle: ![BSA edges demo](Images/API/bsa_edges_demo.png) ### Multisite The `multisite` endpoint is a GPU only function for simulating many transmitters at once. It accepts a JSON object in the request **body** describing an array of `transmitters`, using familiar fields from an `area` call. An antenna is defined for every transmitter so you can have different patterns in a network. You can even model a group of distant radios with omni antennas and a long range parabolic in the same request. It renders the legacy `mesh` API call obsolete (unless you only have a CPU) as it merges sites by design. Transmitters must be within 2000km of each other. eg. You cannot request a multisite across the Ocean. **Antenna azimuths must be between 0 and 359 degrees**. #### Request The below example is for three local UHF radios, with omni dipoles (ant: 1) in the hills. The request is sent as a `POST` request to `https://api.cloudrf.com/multisite` and it will return a JSON response containing metadata and URLs to image layers for your map. ```json { "site": "3amigos", "network": "UHF", "transmitters": [ { "lat": 38.941501808741165, "lon": 1.3709467181497763, "alt": 2, "frq": 868, "txw": 1, "bwi": 0.1, "ant": 0, "antenna": { "txg": 2.15, "txl": 0, "ant": 1, "azi": 0, "tlt": 0, "hbw": 1, "vbw": 1, "fbr": 2.15, "pol": "v" } }, { "lat": 38.94210625018613, "lon": 1.3847431304250852, "alt": 2, "frq": 868, "txw": 1, "bwi": 0.1, "ant": 0, "antenna": { "txg": 2.15, "txl": 0, "ant": 1, "azi": 0, "tlt": 0, "hbw": 1, "vbw": 1, "fbr": 2.15, "pol": "v" } }, { "lat": 38.94370157813472, "lon": 1.4006969012717958, "alt": 2, "frq": 868, "txw": 1, "bwi": 0.1, "ant": 0, "antenna": { "txg": 2.15, "txl": 0, "ant": 1, "azi": 0, "tlt": 0, "hbw": 1, "vbw": 1, "fbr": 2.15, "pol": "v" } } ], "receiver": { "alt": 2, "rxg": 2, "rxs": -105 }, "model": { "pm": 11, "pe": 2, "ked": 1, "rel": 95 }, "environment": { "clm": 0, "cll": 1, "clt": "Minimal.clt" }, "output": { "units": "m", "col": "LTE.dBm", "out": 2, "nf": -120, "res": 10, "rad": 2 } } ``` #### Response The `PNG_Mercator` image is warped for slippy maps like Mapbox, Leaflet and Google Maps. The `PNG_WGS84` is for globes like Google Earth, Cesium and WinTAK. ```json { "kmz": "https://api.cloudrf.com/archive/WUMya3l4a3ZmcVFDajZ4THZVckVuQT09/kmz", "PNG_Mercator": "https://api.cloudrf.com/output/projection/3857/1/0816124833_UAS_PPA_MULTISITE", "PNG_WGS84": "https://api.cloudrf.com/users/1/0816124833_UAS_PPA_MULTISITE.4326.png", "bounds": [ 38.96165650015791, 1.4238207375342375, 38.92354146204287, 1.3478216615351617 ], "id": 8201557, "sid": "WUMya3l4a3ZmcVFDajZ4THZVckVuQT09", "area": 43, "coverage": 0, "key": [ { "l": "-60dBm", "r": 248, "g": 14, "b": 14 }, { "l": "-65dBm", "r": 248, "g": 107, "b": 14 }, { "l": "-70dBm", "r": 247, "g": 199, "b": 14 }, { "l": "-75dBm", "r": 203, "g": 247, "b": 14 }, { "l": "-80dBm", "r": 110, "g": 247, "b": 14 }, { "l": "-85dBm", "r": 17, "g": 246, "b": 13 }, { "l": "-90dBm", "r": 13, "g": 246, "b": 102 }, { "l": "-95dBm", "r": 13, "g": 246, "b": 194 }, { "l": "-100dBm", "r": 13, "g": 205, "b": 245 }, { "l": "-105dBm", "r": 13, "g": 113, "b": 245 } ], "elapsed": 430.0, "balance": 23248 } ``` ![Multisite demo](Images/API/multisite_demo.png) #### RADAR With Mixed Heights Example The below example shows how to use the `multisite` API with RADAR transmitters (`model.pm` value of `8`) where the transmitters are positioned with an elevation relative to the ground (AGL), and the receiver has an elevation above sea level (AMSL). This is indicated with an `output.tx_units` value of `m` and `output.rx_units` value of `m_amsl`, respectively. ```json { "site": "Multisite", "network": "RADAR_TEST", "transmitters": [ { "lat": 30.510523, "lon": -86.830194, "alt": 5, "frq": 8000, "txw": 100, "bwi": 1.2, "antenna": { "txg": 15, "txl": 0, "ant": 1, "azi": 0, "tlt": 0, "hbw": 30, "vbw": 30, "fbr": 0, "pol": "v" } }, { "lat": 30.500163, "lon": -86.814786, "alt": 5, "frq": 8000, "txw": 100, "bwi": 1.2, "antenna": { "txg": 15, "txl": 0, "ant": 1, "azi": 0, "tlt": 0, "hbw": 30, "vbw": 30, "fbr": 0, "pol": "v" } } ], "receiver": { "alt": 500, "rxg": 0, "rxs": -100 }, "model": { "pm": 8, "pe": 2, "ked": 1, "rel": 50, "rcs": 100 }, "environment": { "elevation": 2, "cll": 0, "clm": 0, "clt": "Minimal.clt" }, "output": { "tx_units": "m", "rx_units": "m_amsl", "col": "RAINBOW.dBm", "out": 2, "nf": -120, "res": 50, "rad": 100 } } ``` ### Merge The `merge` function, previously `mesh`, is a legacy function which merges pre-calcuated layers into a composite layer. This function is now done automatically with the `multisite` function but this is still useful for post-processing such as creating network coverage maps. #### Request The below example is a HTTP `GET` request for meshing the layers (max 1000) belonging to the `BLUENET` network and using the name `mymap` for the output. You must send your API key in the request header as normal. ``` https://api.cloudrf.com/mesh?network=BLUENET&name=mymap ``` You can also specify up to 100 sites in the URL as a list: ``` https://api.cloudrf.com/mesh?network=mymap&calcs=0816143545_BLUENET_Site,0816143541_BLUENET_Site ``` #### Response The response is a JSON object containing images and metadata about the calculations which were used to create the mesh image: ```json { "kmz": "https://api.cloudrf.com/archive/WkYveUVDdmZTNG1IbDdPbjVKUVVLUT09/kmz", "PNG_Mercator": "https://api.cloudrf.com/output/projection/3857/1/0816133557_Mesh", "PNG_WGS84": "https://api.cloudrf.com/users/1/0816133557_Mesh.4326.png", "bounds": [ 51.99334, -2.159573, 51.76476, -2.378462 ], "id": 3541, "sid": "WkYveUVDdmZTNG1IbDdPbjVKUVVLUT09", "key": [ { "l": "50dB", "r": 115, "g": 242, "b": 15 }, { "l": "45dB", "r": 150, "g": 243, "b": 14 }, { "l": "40dB", "r": 186, "g": 243, "b": 12 }, { "l": "35dB", "r": 222, "g": 243, "b": 11 }, { "l": "30dB", "r": 243, "g": 228, "b": 11 }, { "l": "25dB", "r": 243, "g": 192, "b": 10 }, { "l": "20dB", "r": 243, "g": 155, "b": 9 }, { "l": "15dB", "r": 243, "g": 118, "b": 9 }, { "l": "10dB", "r": 242, "g": 81, "b": 8 }, { "l": "5dB", "r": 242, "g": 44, "b": 8 }, { "l": "0dB", "r": 242, "g": 7, "b": 7 } ], "elapsed": 697.0, "balance": 10000000000, "resolution": 5 } ``` ![Mesh demo](Images/API/mesh_demo.png) ### Interference The `interference` function compares a set pre-calculated 'signal' sites with a set of pre-calculated 'noise' sites, and calculates a Jamming-to-Signal ratio per pixel. Signal sites can be specified with either a network name `s_network` or an array of site names `s_sites`, interferers are specified similarly with `j_network` and `j_sites`. #### Request The below example is a HTTP `POST` request for comparing the layers (max 1000) belonging to the InterferenceSignal network against three specified layers from the InterferenceJamming network. The name `QRM2` is used for the output, and the `JS.dB` colour scheme is specified. You must send your API key in the request header as normal. ```json { "s_network": "InterferenceSignal", "j_sites": [ "1125164804_InterferenceJamming_A", "1125164820_InterferenceJamming_B", "1125164852_InterferenceJamming_D" ], "name": "InterferenceExample", "colour_key": "JS.dB" } ``` #### Response The response is a JSON object containing images and metadata about the calculations which were used to create the interference image: ```json { "kmz": "https://localhost/archive/M3paazRnWE84Nk0xWWc2d2RzT05zZz09/kmz", "PNG_Mercator": "https://localhost/output/projection/3857/1/1126160715_InterferenceExample_QRM", "PNG_WGS84": "https://localhost/users/1/1126160715_InterferencExample_QRM.4326.png", "bounds": [ 51.99334, -2.159573, 51.76476, -2.378462 ], "id": 3541, "sid": "M3paazRnWE84TG4xWWc2d2RzT05zZz09", "key": [ { "l": "12dB", "r": 254, "g": 0, "b": 0 }, { "l": "11dB", "r": 254, "g": 21, "b": 0 }, { "l": "10dB", "r": 254, "g": 43, "b": 0 }, { "l": "9dB", "r": 254, "g": 64, "b": 0 }, { "l": "8dB", "r": 254, "g": 85, "b": 0 }, { "l": "7dB", "r": 254, "g": 106, "b": 0 }, { "l": "6dB", "r": 254, "g": 128, "b": 0 }, { "l": "5dB", "r": 254, "g": 149, "b": 0 }, { "l": "4dB", "r": 254, "g": 170, "b": 0 }, { "l": "3dB", "r": 254, "g": 191, "b": 0 }, { "l": "2dB", "r": 254, "g": 212, "b": 0 }, { "l": "1dB", "r": 254, "g": 234, "b": 0 }, { "l": "0dB", "r": 254, "g": 254, "b": 0 }, { "l": "-1dB", "r": 234, "g": 254, "b": 0 }, { "l": "-2dB", "r": 213, "g": 254, "b": 0 }, { "l": "-3dB", "r": 191, "g": 254, "b": 0 }, { "l": "-4dB", "r": 170, "g": 254, "b": 0 }, { "l": "-5dB", "r": 149, "g": 254, "b": 0 }, { "l": "-6dB", "r": 128, "g": 254, "b": 0 }, { "l": "-7dB", "r": 106, "g": 254, "b": 0 }, { "l": "-8dB", "r": 85, "g": 254, "b": 0 }, { "l": "-9dB", "r": 64, "g": 254, "b": 0 }, { "l": "-10dB", "r": 43, "g": 254, "b": 0 }, { "l": "-11dB", "r": 21, "g": 254, "b": 0 }, { "l": "-12dB", "r": 0, "g": 254, "b": 0 } ], "elapsed": 810.0, "balance": 10000000000, "resolution": 10, "percentages_above_js": [ { "js": 12.0, "percentage": 11.737680295098624 }, { "js": 9.0, "percentage": 21.294148830428732 }, { "js": 6.0, "percentage": 32.65301614648908 }, { "js": 3.0, "percentage": 45.59119121772359 }, { "js": 0.0, "percentage": 59.5531553022773 }, { "js": -3.0, "percentage": 71.56362511872419 }, { "js": -6.0, "percentage": 81.5077419212334 }, { "js": -9.0, "percentage": 88.70850175601352 }, { "js": -12.0, "percentage": 93.6209219623175 } ] } ``` ![QRM demo](Images/API/interference_demo.png) ### Network The `network` function is a legacy function which tests links to pre-calcuated sites to reveal the strongest server(s) at the tested receiver site (eg. a customer's house). #### Request The below example is a HTTP `GET` request for testing a location with receiver gain 2dBi and receiver height 2m to the BLUENET network. You must send your API key in the request header as normal. ``` https://api.cloudrf.com/network?lat=38.938322&lon=1.398755&net=BLUENET&rxg=2&rxh=2 ``` #### Response The response is a verbose JSON object containing metadata about each link tested. This can be parsed to reveal the required information eg. RSSI so you can plot coloured links for example: ```json [ { "Engine": "Sleipnir 1.7.8", "Frequency MHz": 868, "Propagation model": "Egli VHF/UHF", "Model subtype": "Suburban", "Receiver": [ { "Latitude": 38.93832, "Longitude": 1.398755, "Ground elevation m": 91, "Antenna height m": 2, "Receiver gain dBd": -0.15, "Receiver gain dBi": 2 } ], "Transmitters": [ { "Latitude": 38.94411, "Longitude": 1.399756, "Ground elevation m": 92, "Antenna height m": 2, "Distance to receiver km": 0.651, "Azimuth to receiver deg": 187.65, "Downtilt angle deg": 0.1, "Antenna gain dBd": 0, "Antenna gain dBi": 2.15, "Polarisation": "Vertical", "Power W": 1, "Power dBm": 32.15, "ERP W": 1, "EIRP W": 1.641, "ERP dBm": 30, "EIRP dBm": 32.15, "Free space path loss dB": 83.3, "Bandwidth MHz": 0.1, "Johnson Nyquist noise dB": 0.2, "Noise floor dBm": -115, "Channel noise dBm": -114.8, "Signal power at receiver dBm": -87.2, "Signal to Noise Ratio dB": 27.6, "Computed path loss dB": 117.2, "Model attenuation dB": 33.9, "Field strength at receiver dBuV/m": 50.9, "RX voltage 50 ohm dipole uV": 16, "RX voltage 50 ohm dipole dBuV": 24, "RX voltage 75 ohm dipole uV": 20, "RX voltage 75 ohm dipole dBuV": 26, "Raise RX antenna for LOS": 102, "Raise RX antenna for fresnel 60%": 313, "Raise RX antenna for full fresnel": 344, "Obstructions": [ [ 38.93995, 1.399037 ], [ 38.94017, 1.399075 ], [ 38.94039, 1.399113 ], ... snipped for brevity ... ], "Distance": [ 0.012, 0.025, 0.037, 0.049, 0.062, ... snipped for brevity ... ], "Terrain": [ 102, 103, 108, 109, 112, ... snipped for brevity ... ], "Terrain_AMSL": [ 101, 102, 104, 108, 109, ... snipped for brevity ... ], "Fresnel": [ 0, -2.04, -2.86, -3.47, -3.96, ... snipped for brevity ... ], "dBm": [ -21, -31, -38, -42, -46, ... snipped for brevity ... ], "dB": [ 51, 61, 68, 72, 76, ... snipped for brevity ... ] } ], "Chart image": "https://api.cloudrf.com/API/archive/data?ppa=5d0ffce3&uid=1", "Network KML": "https://api.cloudrf.com/users/1/b7ce7077.kml", "Server ID": 8201612, "Server name": "0816143545_BLUENET_Site" }, { "Engine": "Sleipnir 1.7.8", "Frequency MHz": 868, "Propagation model": "Egli VHF/UHF", "Model subtype": "Suburban", "Receiver": [ { "Latitude": 38.93832, "Longitude": 1.398755, "Ground elevation m": 91, "Antenna height m": 2, "Receiver gain dBd": -0.15, "Receiver gain dBi": 2 } ], "Transmitters": [ { "Latitude": 38.94279, "Longitude": 1.386807, "Ground elevation m": 117, "Antenna height m": 2, "Distance to receiver km": 1.148, "Azimuth to receiver deg": 115.66, "Downtilt angle deg": 1.3, "Antenna gain dBd": 0, "Antenna gain dBi": 2.15, "Polarisation": "Vertical", "Power W": 1, "Power dBm": 32.15, "ERP W": 1, "EIRP W": 1.641, "ERP dBm": 30, "EIRP dBm": 32.15, "Free space path loss dB": 88.3, "Bandwidth MHz": 0.1, "Johnson Nyquist noise dB": 0.2, "Noise floor dBm": -115, "Channel noise dBm": -114.8, "Signal power at receiver dBm": -96.9, "Signal to Noise Ratio dB": 17.9, "Computed path loss dB": 126.9, "Model attenuation dB": 38.6, "Field strength at receiver dBuV/m": 41.3, "RX voltage 50 ohm dipole uV": 5, "RX voltage 50 ohm dipole dBuV": 14, "RX voltage 75 ohm dipole uV": 6, "RX voltage 75 ohm dipole dBuV": 16, "Raise RX antenna for LOS": 80, "Raise RX antenna for fresnel 60%": 92, "Raise RX antenna for full fresnel": 100, "Obstructions": [ [ 38.93835, 1.398676 ], [ 38.93843, 1.398467 ], [ 38.93847, 1.398363 ] ... snipped for brevity ... ], "Distance": [ 0.01, 0.02, 0.03, 0.04, 0.05, ... snipped for brevity ... ], "Terrain": [ 111, 111, 110, 110, 110, ... snipped for brevity ... ], "Terrain_AMSL": [ 114, 112, 112, 111, 111, ... snipped for brevity ... ], "Fresnel": [ 0, -1.85, -2.61, -3.18, -3.65, ... snipped for brevity ... ], "dBm": [ -16, -27, -34, -39, -43, ... snipped for brevity ... ], "dB": [ 46, 57, 64, 69, 73, ... snipped for brevity ... ] } ], "Chart image": "https://api.cloudrf.com/API/archive/data?ppa=3a949f40&uid=1", "Network KML": "https://api.cloudrf.com/users/1/b7ce7077.kml", "Server ID": 8201611, "Server name": "0816143541_BLUENET_Site" } ] ``` ![Network demo](Images/API/network_demo.jpg) ### HF Area The `/hf/area` endpoint accepts a JSON object in the request **body** describing your network and will run a point-to-multipoint "heatmap" calculation using VOACAP. #### Request The below example is for a 4MHz signal at 00:00 UTC in July. The request is sent as a `POST` request to `https://api.cloudrf.com/hf/area` and it will return a JSON response containing metadata and URLs to image layers for your map. ```json { "site": "Test", "network": "HFTest", "transmitter": { "lat": 51.833992, "lon": -2.231319, "frq": 4.0 }, "antenna": { "txg": 2.0, "txl": 0.0, "ant": 1, "pol": "h", "azi": 90 }, "model": { "month": 7, "hour": 0, "rel": 50 }, "output": { "col": "SNR1.dB", "bounds": { "north": "72.499717", "east": "40.944650", "south": "6.353099", "west": "-60.400436" }, "nf": -140, "out": 4 } } ``` #### Response The `PNG_Mercator` image is warped for slippy maps like Mapbox, Leaflet and Google Maps. The `PNG_WGS84` is for globes like Google Earth, Cesium and WinTAK. ```json { "kmz": "https://api.cloudrf.com/archive/M2JUbU5yOU5XNFFWZ29JZjNmaFFUdz09/kmz", "PNG_Mercator": "https://api.cloudrf.com/output/projection/3857/1/1017080906_HFTest_Test_HF", "PNG_WGS84": "https://api.cloudrf.com/users/1/1017080906_HFTest_Test_HF.4326.png", "bounds": [ 72.499717, 40.94465, 6.353099, -60.400436 ], "id": 2025, "sid": "M2JUbU5yOU5XNFFWZ29JZjNmaFFUdz09", "key": [ { "l": "40dB", "r": 37, "g": 131, "b": 255 }, { "l": "30dB", "r": 49, "g": 254, "b": 127 }, { "l": "20dB", "r": 218, "g": 254, "b": 60 }, { "l": "10dB", "r": 254, "g": 72, "b": 72 } ], "elapsed": 2364.0, "balance": 10000000000 } ``` PNG image with colours which map to the signal levels in the JSON output: ![HF area demo](Images/API/hf_area_demo.png) ### HF Prediction The `/hf/prediction` endpoint accepts a JSON object in the request **body** describing your network and will run a point-to-point calculation using VOACAP. It produces an output for multiple frequencies, and so can be used for frequency selection. #### Request The below example is for a 4MHz signal in March. The request is sent as a `POST` request to `https://api.cloudrf.com/hf/prediction` and it will return a JSON response containing a link to a chart png. ```json { "site": "Test", "network": "HFTest", "transmitter": { "lat": "51.8656", "lon": "-2.207", "frq": 4.0, "txw": 10, "alt": 6.0 }, "receiver": { "lat": "41.86", "lon": "12.78", "rxg": "2.15" }, "antenna": { "txg": 2.15, "ant": 2 }, "model": { "month": 3, "sunspots_r12": 50 }, "output": { "nf": -140 } } ``` #### Response ```json { "Transmitters": [ { "Latitude": 51.8656, "Longitude": -2.207 } ], "Receiver": [ { "Latitude": 41.86, "Longitude": 12.78 } ], "Chart image": "https://api.cloudrf.com/users/1/1029125124_HFTest_Test_HF_PPA.png", "elapsed": 1295.0, "balance": 10000000000, "metadata": { "locations": { "transmitter": { "latitude": 51.8656, "longitude": -2.207, "altitude_m": 6.0 }, "receiver": { "latitude": 41.86, "longitude": 12.78 }, "distance_km": 1586.7 }, "antenna": { "name": "ITSA-1 Horizontal Dipole", "gain": 2.15 }, "month": "March", "power_w": 10.0 }, "hours": { "hour0": [ { "frequency": 2.0, "snr": 14.0 }, { "frequency": 5.0, "snr": 22.0 }, { "frequency": 8.0, "snr": 27.0 }, { "frequency": 11.0, "snr": 7.0 }, { "frequency": 14.0, "snr": -32.0 }, { "frequency": 17.0, "snr": -77.0 }, { "frequency": 20.0, "snr": -76.0 }, { "frequency": 4.0, "snr": 20.0 } ], "hour1": [ { "frequency": 2.0, "snr": 17.0 }, { "frequency": 5.0, "snr": 26.0 }, { "frequency": 8.0, "snr": 30.0 }, { "frequency": 11.0, "snr": -6.0 }, { "frequency": 14.0, "snr": -74.0 }, { "frequency": 17.0, "snr": -74.0 }, { "frequency": 20.0, "snr": -73.0 }, { "frequency": 4.0, "snr": 24.0 } ], ...snipped for brevity... } } ``` Chart image showing which frequencies are stronger at different times of day: ![HF prediction demo](Images/API/hf_prediction_demo.png) ## Area API with bounded output Often the study area is far away from the transmitter, for example a suburb which is served by a mountain repeater. By bounding the output area in the request, only this focus area is computed and returned. Aside from being very fast and efficient, it allows for higher resolution calculations than would be possible by testing the entire town / valley in all directions. For example, to test an airfield with LiDAR accuracy that is 42km away from the transmitter you would populate the "bounds" array within the "output" section of an area request. ``` { ... snipped for brevity ... "output": { "units": "m", "col": "3", "out": "2", "ber": "1", "mod": null, "nf": "-120", "res": "2", "rad": "42", "bounds": { "north": 51.677, "south": 51.655, "east": -2.039, "west": -2.076 } } } ``` ## More Information For a complete OpenAPI 3 schema of the CloudRF API please consult the [OpenAPI documentation](https://cloudrf.com/documentation/developer/). ### Compression CloudRF uses gzip compression for output files. The official client handles this but if you are writing your own you should use the MIME type rather than writing raw data as a `.tiff` may actually be .`tiff.gz`. Below shows an example of this in Python 3: ``` # WARNING: Will write a gzipped file shutil.copyfileobj(response.raw, outputFile) # Will handle gzip decompression outputFile.write(response.content) ``` ### Verbose Environment Variables Since `v3.8`, the `environment` block has been redesigned to replace the cryptic `cll` and `clm` values with simpler boolean layers describing `elevation`, `landcover`, `buildings` and `obstacles`. This enables easier layering of custom clutter upon DTM for example (elevation = `2`, obstacles = `1`). Both methods are supported in `3.8` but the long form is now the standard, as used in the web interface, and the old trigraph method is deprecated. It still features in places such as templates. The following table translates old to new values: | Legacy Value | New Value (3.8) | | ------------- | ---------------------------- | | cll = 0 | landcover = 0 | | cll = 1 | landcover = 1 | | cll = 2 | landcover = 1, buildings = 1 | | clm = 0 | obstacles = 0 | | clm = 1 | obstacles = 1 | | clm = 2 | elevation = 2, obstacles = 1 | ## API scripts A list of ready-to-use scripts are available on the [CloudRF public GitHub repository](https://github.com/Cloud-RF/CloudRF-API-clients/). - [Python](https://github.com/Cloud-RF/CloudRF-API-clients/tree/master/python) - [Bash](https://github.com/Cloud-RF/CloudRF-API-clients/tree/master/bash) - [Slippy maps](https://github.com/Cloud-RF/CloudRF-API-clients/tree/master/slippy-maps) Some radio templates [are here](https://github.com/Cloud-RF/CloudRF-API-clients/tree/master/templates) to help you pick good settings for hardware: ## Trouble shooting The most common reasons for API support tickets are below. - Commas instead of decimals in floating point numbers eg. 446,000 - Missing columns or rows in CSV data - Rate limiting errors triggered by a small radius and large resolution. Eg. 1km at 90m - Bad antenna name eg. dypole vs DIPOLE.ADF - For automatic processing in the UI, the **CSV field must match a form input** in the interface. Eg. `dbm` should be `Received Power (dBm)`