Shape mapping, only return certain type(s) on a query

I don't see a way for a query on mapping type shape to only return certain shapes. Something like this:

POST /example/_doc
{
  "location" : {
    "type" : "point",
    "coordinates" : [-377.03653, 389.897676]
  },
 "return": {
  "type": "POLYGON"
}

Is this possible?

Welcome!

No I don't think this is doable.
The geo_shape query will return whatever shape which is matching.

type sub field is not accessible for search but seems to be used only at index time.

So this does not work:

DELETE /example
PUT /example
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_shape"
      }
    }
  }
}

POST /example/_doc?refresh
{
  "name": "Wind & Wetter, Berlin, Germany",
  "location": {
    "type": "point",
    "coordinates": [ 13.400544, 52.530286 ]
  }
}
GET /example/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "location.type": {
              "value": "point"
            }
          }
        },
        {
          "geo_shape": {
            "location": {
              "shape": {
                "type": "envelope",
                "coordinates": [ [ 13.0, 53.0 ], [ 14.0, 52.0 ] ]
              },
              "relation": "within"
            }
          }
        }
      ]
    }
  }
}

But if you copy the type field to another field, like location_type, this is working.

POST /example/_doc?refresh
{
  "name": "Wind & Wetter, Berlin, Germany",
  "location_type": "point",
  "location": {
    "type": "point",
    "coordinates": [ 13.400544, 52.530286 ]
  }
}
GET /example/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "location_type": {
              "value": "point"
            }
          }
        },
        {
          "geo_shape": {
            "location": {
              "shape": {
                "type": "envelope",
                "coordinates": [ [ 13.0, 53.0 ], [ 14.0, 52.0 ] ]
              },
              "relation": "within"
            }
          }
        }
      ]
    }
  }
}

You might be able to automate that, using an ingest pipeline with a set processor...

Darn. It would be really useful and powerful if this was a thing because then you could only return certain types without having to bloat the doc.

Thank you for looking into this!

May be open an issue?

This can actually be solved using a runtime field. For example something like:

GET example/_search
{
  "runtime_mappings": {
    "geometry_type": {
      "type": "keyword",
      "script": {
        "source": "emit(params._source.location.type)"
      }
    }
  },
  "query": {
    "term": {
      "geometry_type": {
        "value": "point"
      }
    }
  }
}

This will be slow but you can add it to the mapping to get it indexed:

PUT /example
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_shape"
      },
      "geometry_type": {
        "type": "keyword",
        "script": {
          "source": "emit(params._source.location.type)"
        }
      }
    }
  }
}

Sorry but this did not work for me. When putting the mapping, I get this error:

{
  "error": {
    "root_cause": [
      {
        "type": "mapper_parsing_exception",
        "reason": "Root mapping definition has unsupported parameters:  [geometry_type : {type=keyword, script={source=emit(params._source.location.type)}}] [location : {type=geo_shape}]"
      }
    ],
    "type": "mapper_parsing_exception",
    "reason": "Failed to parse mapping: Root mapping definition has unsupported parameters:  [geometry_type : {type=keyword, script={source=emit(params._source.location.type)}}] [location : {type=geo_shape}]",
    "caused_by": {
      "type": "mapper_parsing_exception",
      "reason": "Root mapping definition has unsupported parameters:  [geometry_type : {type=keyword, script={source=emit(params._source.location.type)}}] [location : {type=geo_shape}]"
    }
  },
  "status": 400
}

I am running elasticsearch 8.6.2

Are you updating a mapping? then the syntax should belike:

PUT /example/_mapping
{
    "properties": {
      "location": {
        "type": "geo_shape"
      },
      "geometry_type": {
        "type": "keyword",
        "script": {
          "source": "emit(params._source.location.type)"
        }
      }
    }
}

whoops, was being an idiot with my mapping, but this solution still doesn't work because some of my docs' geometry are in WKT, so I get this error

{
  "error": {
    "root_cause": [
      {
        "type": "script_exception",
        "reason": "runtime error",
        "script_stack": [
          "emit(params._source.location.type)",
          "                            ^---- HERE"
        ],
        "script": "emit(params._source.location.type)",
        "lang": "painless",
        "position": {
          "offset": 28,
          "start": 0,
          "end": 34
        }
      }
    ],
    "type": "mapper_parsing_exception",
    "reason": "Error executing script on field [geometry_type]",
    "caused_by": {
      "type": "script_exception",
      "reason": "runtime error",
      "script_stack": [
        "emit(params._source.location.type)",
        "                            ^---- HERE"
      ],
      "script": "emit(params._source.location.type)",
      "lang": "painless",
      "position": {
        "offset": 28,
        "start": 0,
        "end": 34
      },
      "caused_by": {
        "type": "illegal_argument_exception",
        "reason": "dynamic getter [java.lang.String, type] not found"
      }
    }
  },
  "status": 400
}

Basically saying it is looking for type but in WKT, there is no type.

I did not mean to give a full solution but a pointer how to overcome the issue. You can still support both formats with a more complex function:

PUT /example/_mapping
{
  "properties": {
    "location": {
      "type": "geo_shape"
    },
    "geometry_type": {
      "type": "keyword",
      "script": {
        "source": """
          if (params._source.location instanceof String) {
            def s = params._source.location;
            emit(s.substring(0, s.indexOf('(')).trim().toLowerCase(Locale.ROOT));
          } else {
             emit(params._source.location.type.toLowerCase(Locale.ROOT));
          }
          """
      }
    }
  }
}