use crate::{ArcGISGeometry, GeometryType, SpatialReference};
use derive_getters::Getters;
use derive_setters::Setters;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use tokio::io::AsyncWrite;
use super::{ImageFormat, LayerSelection, ResponseFormat, TimeRelation};
pub enum ExportTarget {
Path(PathBuf),
Bytes,
Writer(Box<dyn AsyncWrite + Send + Sync + Unpin>),
}
impl ExportTarget {
pub fn to_path(path: impl Into<PathBuf>) -> Self {
Self::Path(path.into())
}
pub fn to_bytes() -> Self {
Self::Bytes
}
pub fn to_writer<W>(writer: W) -> Self
where
W: AsyncWrite + Send + Sync + Unpin + 'static,
{
Self::Writer(Box::new(writer))
}
}
pub enum ExportResult {
Path(PathBuf),
Bytes(Vec<u8>),
Written(u64),
}
impl ExportResult {
pub fn path(&self) -> Option<&PathBuf> {
match self {
Self::Path(p) => Some(p),
_ => None,
}
}
pub fn bytes(&self) -> Option<&[u8]> {
match self {
Self::Bytes(b) => Some(b),
_ => None,
}
}
pub fn written(&self) -> Option<u64> {
match self {
Self::Written(n) => Some(*n),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LayerDefinitions {
definitions: HashMap<i32, String>,
}
impl LayerDefinitions {
pub fn new() -> Self {
Self::default()
}
pub fn add_layer_def(mut self, layer_id: i32, where_clause: impl Into<String>) -> Self {
self.definitions.insert(layer_id, where_clause.into());
self
}
pub fn add_layer_def_mut(&mut self, layer_id: i32, where_clause: impl Into<String>) {
self.definitions.insert(layer_id, where_clause.into());
}
pub fn build(&self) -> String {
serde_json::to_string(&self.definitions).unwrap_or_else(|_| "{}".to_string())
}
pub fn is_empty(&self) -> bool {
self.definitions.is_empty()
}
pub fn len(&self) -> usize {
self.definitions.len()
}
pub fn get(&self, layer_id: i32) -> Option<&String> {
self.definitions.get(&layer_id)
}
}
impl From<LayerDefinitions> for String {
fn from(defs: LayerDefinitions) -> Self {
defs.build()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Getters)]
pub struct TileCoordinate {
level: u32,
row: u32,
col: u32,
}
impl TileCoordinate {
pub fn new(level: u32, row: u32, col: u32) -> Self {
Self { level, row, col }
}
}
#[derive(Debug, Clone, Serialize, derive_builder::Builder, derive_getters::Getters, Setters)]
#[builder(setter(into, strip_option), default)]
#[setters(prefix = "set_", borrow_self)]
pub struct ExportMapParams {
bbox: String,
#[serde(rename = "bboxSR", skip_serializing_if = "Option::is_none")]
bbox_sr: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
layers: Option<String>,
#[serde(rename = "layerDefs", skip_serializing_if = "Option::is_none")]
layer_defs: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
size: Option<String>,
#[serde(rename = "imageSR", skip_serializing_if = "Option::is_none")]
image_sr: Option<i32>,
#[serde(rename = "historicMoment", skip_serializing_if = "Option::is_none")]
historic_moment: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
format: Option<ImageFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
transparent: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
dpi: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
time: Option<String>,
#[serde(rename = "timeRelation", skip_serializing_if = "Option::is_none")]
time_relation: Option<TimeRelation>,
#[serde(rename = "layerTimeOptions", skip_serializing_if = "Option::is_none")]
layer_time_options: Option<String>,
#[serde(rename = "dynamicLayers", skip_serializing_if = "Option::is_none")]
dynamic_layers: Option<String>,
#[serde(rename = "gdbVersion", skip_serializing_if = "Option::is_none")]
gdb_version: Option<String>,
#[serde(rename = "mapScale", skip_serializing_if = "Option::is_none")]
map_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
rotation: Option<f64>,
#[serde(
rename = "datumTransformations",
skip_serializing_if = "Option::is_none"
)]
datum_transformations: Option<String>,
#[serde(
rename = "layerParameterValues",
skip_serializing_if = "Option::is_none"
)]
layer_parameter_values: Option<String>,
#[serde(rename = "mapRangeValues", skip_serializing_if = "Option::is_none")]
map_range_values: Option<String>,
#[serde(rename = "layerRangeValues", skip_serializing_if = "Option::is_none")]
layer_range_values: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
clipping: Option<String>,
#[serde(rename = "spatialFilter", skip_serializing_if = "Option::is_none")]
spatial_filter: Option<String>,
#[serde(
rename = "selectionDefinitions",
skip_serializing_if = "Option::is_none"
)]
selection_definitions: Option<String>,
#[serde(rename = "f")]
#[builder(default = "ResponseFormat::Json")]
format_response: ResponseFormat,
}
impl Default for ExportMapParams {
fn default() -> Self {
Self {
bbox: String::new(),
bbox_sr: None,
layers: None,
layer_defs: None,
size: Some("400,400".to_string()),
image_sr: None,
historic_moment: None,
format: Some(ImageFormat::Png),
transparent: Some(false),
dpi: Some(96),
time: None,
time_relation: None,
layer_time_options: None,
dynamic_layers: None,
gdb_version: None,
map_scale: None,
rotation: None,
datum_transformations: None,
layer_parameter_values: None,
map_range_values: None,
layer_range_values: None,
clipping: None,
spatial_filter: None,
selection_definitions: None,
format_response: ResponseFormat::Json,
}
}
}
impl ExportMapParams {
pub fn builder() -> ExportMapParamsBuilder {
ExportMapParamsBuilder::default()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
pub struct ExportExtent {
xmin: f64,
ymin: f64,
xmax: f64,
ymax: f64,
#[serde(rename = "spatialReference", skip_serializing_if = "Option::is_none")]
spatial_reference: Option<SpatialReference>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
pub struct ExportMapResponse {
href: String,
width: i32,
height: i32,
#[serde(skip_serializing_if = "Option::is_none")]
extent: Option<ExportExtent>,
#[serde(skip_serializing_if = "Option::is_none")]
scale: Option<f64>,
}
#[derive(Debug, Clone, Serialize, derive_builder::Builder, derive_getters::Getters)]
#[builder(setter(into, strip_option), default)]
pub struct IdentifyParams {
geometry: String,
#[serde(rename = "geometryType")]
geometry_type: GeometryType,
tolerance: i32,
#[serde(rename = "mapExtent")]
map_extent: String,
#[serde(rename = "imageDisplay")]
image_display: String,
#[serde(skip_serializing_if = "Option::is_none")]
sr: Option<i32>,
#[serde(rename = "layerDefs", skip_serializing_if = "Option::is_none")]
layer_defs: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
time: Option<String>,
#[serde(rename = "timeRelation", skip_serializing_if = "Option::is_none")]
time_relation: Option<TimeRelation>,
#[serde(skip_serializing_if = "Option::is_none")]
layers: Option<LayerSelection>,
#[serde(rename = "returnGeometry", skip_serializing_if = "Option::is_none")]
return_geometry: Option<bool>,
#[serde(rename = "maxAllowableOffset", skip_serializing_if = "Option::is_none")]
max_allowable_offset: Option<f64>,
#[serde(rename = "geometryPrecision", skip_serializing_if = "Option::is_none")]
geometry_precision: Option<i32>,
#[serde(rename = "returnZ", skip_serializing_if = "Option::is_none")]
return_z: Option<bool>,
#[serde(rename = "returnM", skip_serializing_if = "Option::is_none")]
return_m: Option<bool>,
#[serde(rename = "gdbVersion", skip_serializing_if = "Option::is_none")]
gdb_version: Option<String>,
#[serde(
rename = "returnUnformattedValues",
skip_serializing_if = "Option::is_none"
)]
return_unformatted_values: Option<bool>,
#[serde(rename = "returnFieldName", skip_serializing_if = "Option::is_none")]
return_field_name: Option<bool>,
#[serde(rename = "f")]
#[builder(default = "ResponseFormat::Json")]
format: ResponseFormat,
#[serde(rename = "dynamicLayers", skip_serializing_if = "Option::is_none")]
dynamic_layers: Option<String>,
#[serde(rename = "layerTimeOptions", skip_serializing_if = "Option::is_none")]
layer_time_options: Option<String>,
#[serde(
rename = "datumTransformations",
skip_serializing_if = "Option::is_none"
)]
datum_transformations: Option<String>,
#[serde(rename = "mapRangeValues", skip_serializing_if = "Option::is_none")]
map_range_values: Option<String>,
#[serde(rename = "layerRangeValues", skip_serializing_if = "Option::is_none")]
layer_range_values: Option<String>,
#[serde(
rename = "layerParameterValues",
skip_serializing_if = "Option::is_none"
)]
layer_parameter_values: Option<String>,
#[serde(rename = "historicMoment", skip_serializing_if = "Option::is_none")]
historic_moment: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
clipping: Option<String>,
#[serde(rename = "spatialFilter", skip_serializing_if = "Option::is_none")]
spatial_filter: Option<String>,
}
impl Default for IdentifyParams {
fn default() -> Self {
Self {
geometry: String::new(),
geometry_type: GeometryType::Point,
tolerance: 3,
map_extent: String::new(),
image_display: String::new(),
sr: None,
layer_defs: None,
time: None,
time_relation: None,
layers: None,
return_geometry: None,
max_allowable_offset: None,
geometry_precision: None,
return_z: None,
return_m: None,
gdb_version: None,
return_unformatted_values: None,
return_field_name: None,
format: ResponseFormat::Json,
dynamic_layers: None,
layer_time_options: None,
datum_transformations: None,
map_range_values: None,
layer_range_values: None,
layer_parameter_values: None,
historic_moment: None,
clipping: None,
spatial_filter: None,
}
}
}
impl IdentifyParams {
pub fn builder() -> IdentifyParamsBuilder {
IdentifyParamsBuilder::default()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct IdentifyResult {
layer_id: i32,
layer_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
display_field_name: Option<String>,
#[serde(default)]
attributes: HashMap<String, serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
geometry: Option<ArcGISGeometry>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
geometry_type: Option<GeometryType>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
pub struct IdentifyResponse {
results: Vec<IdentifyResult>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct LegendSymbol {
label: String,
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
image_data: Option<String>,
content_type: String,
height: i32,
width: i32,
#[serde(skip_serializing_if = "Option::is_none")]
values: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct LayerLegend {
layer_id: i32,
layer_name: String,
layer_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
min_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
max_scale: Option<f64>,
legend: Vec<LegendSymbol>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
pub struct LegendResponse {
layers: Vec<LayerLegend>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
pub struct LevelOfDetail {
level: i32,
resolution: f64,
scale: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct TileInfo {
rows: i32,
cols: i32,
dpi: i32,
format: String,
#[serde(default)]
lods: Vec<LevelOfDetail>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct ServiceLayer {
id: i32,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
parent_layer_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
default_visibility: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
min_scale: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
max_scale: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct MapServiceMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
service_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
map_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
capabilities: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
supported_image_format_types: Option<String>,
#[serde(default)]
layers: Vec<ServiceLayer>,
#[serde(skip_serializing_if = "Option::is_none")]
spatial_reference: Option<SpatialReference>,
#[serde(skip_serializing_if = "Option::is_none")]
initial_extent: Option<ExportExtent>,
#[serde(skip_serializing_if = "Option::is_none")]
full_extent: Option<ExportExtent>,
#[serde(skip_serializing_if = "Option::is_none")]
tile_info: Option<TileInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
single_fused_map_cache: Option<bool>,
}
impl MapServiceMetadata {
pub fn supports_identify(&self) -> bool {
self.supports_capability("Query") && !self.is_cached()
}
pub fn supports_find(&self) -> bool {
self.supports_capability("Query") && !self.is_cached()
}
pub fn supports_legend(&self) -> bool {
!self.is_cached() || self.tile_info().is_some()
}
pub fn supports_export(&self) -> bool {
self.supports_capability("Map")
}
pub fn is_cached(&self) -> bool {
self.single_fused_map_cache.unwrap_or(false)
}
pub fn supports_capability(&self, capability: &str) -> bool {
self.capabilities
.as_ref()
.map(|caps| caps.split(',').any(|c| c.trim() == capability))
.unwrap_or(false)
}
}
#[derive(Debug, Clone, Serialize, derive_builder::Builder, Getters)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct FindParams {
search_text: String,
#[serde(serialize_with = "serialize_vec_as_comma")]
search_fields: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(serialize_with = "serialize_opt_vec_as_comma")]
#[builder(default)]
layers: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
contains: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
return_geometry: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
sr: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
layer_defs: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
return_z: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
return_m: Option<bool>,
}
impl FindParams {
pub fn builder() -> FindParamsBuilder {
FindParamsBuilder::default()
}
}
fn serialize_vec_as_comma<S>(vec: &[String], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&vec.join(","))
}
fn serialize_opt_vec_as_comma<S>(vec: &Option<Vec<i32>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match vec {
Some(v) => {
let s = v
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(",");
serializer.serialize_str(&s)
}
None => serializer.serialize_none(),
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
pub struct FindResponse {
results: Vec<FindResult>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct FindResult {
layer_id: i32,
layer_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
display_field_name: Option<String>,
found_field_name: String,
value: serde_json::Value,
attributes: HashMap<String, serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
geometry: Option<ArcGISGeometry>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
geometry_type: Option<GeometryType>,
}
#[derive(Debug, Clone, Serialize, derive_builder::Builder, Getters)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct GenerateKmlParams {
doc_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(serialize_with = "serialize_opt_vec_as_comma")]
#[builder(default)]
layers: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
layer_defs: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
image_format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
dpi: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
image_size: Option<String>,
}
impl GenerateKmlParams {
pub fn builder() -> GenerateKmlParamsBuilder {
GenerateKmlParamsBuilder::default()
}
}
#[derive(Debug, Clone, Serialize, derive_builder::Builder, Getters)]
#[builder(setter(into, strip_option))]
#[serde(rename_all = "camelCase")]
pub struct GenerateRendererParams {
classification_field: String,
classification_method: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
break_count: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
normalization_field: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
normalization_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
base_symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default)]
color_ramp: Option<serde_json::Value>,
}
impl GenerateRendererParams {
pub fn builder() -> GenerateRendererParamsBuilder {
GenerateRendererParamsBuilder::default()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct RendererResponse {
#[serde(rename = "type")]
renderer_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
field: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
default_symbol: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
default_label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
class_break_infos: Option<Vec<ClassBreakInfo>>,
#[serde(skip_serializing_if = "Option::is_none")]
unique_value_infos: Option<Vec<UniqueValueInfo>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct ClassBreakInfo {
class_min_value: f64,
class_max_value: f64,
label: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
symbol: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Getters)]
#[serde(rename_all = "camelCase")]
pub struct UniqueValueInfo {
value: serde_json::Value,
label: String,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
symbol: serde_json::Value,
}