Olá pessoal!

Aqui na SIGMA sempre estamos desenvolvendo ferramentas úteis para nós e que talvez também sejam úteis para outras pessoas.

Nossa ideia principal, é contribuir um pouco com esse enorme ecossistema Python/Django existente. Como já disseram, estamos sobre os ombros de gigantes.

Bem, desenvolvemos o Django Topology com o objetivo de simplificar a validação de dados geométricos/geográficos que encontramos em nosso dia a dia.

É muito comum, por exemplo, ter de validar a entrada de um polígono e precisamos garantir que ele esteja dentro de outro polígono. Ou garantir que este polígono específico não esteja sobrepondo outro polígono de uma mesma classe.

Para isso desenvolvemos o Django Topology. Através dele você consegue configurar suas validações dentro do banco de dados e executá-las de forma simples.

O fluxo é simples e o pacote já conta com três validações:

  • must be contained - garante que o modelo A deve estar contido no modelo B;
  • must not overlap - garante que o modelo A não pode sobrepor nenhum outro registro em A;
  • must not overlap with - garante que o modelo A não pode sobrepor o modelo B;

O pacote é facilmente extensível e você desenvolver suas próprias validações topológicas.

Um exemplo simples:

        rule = Rule(
            name='regra de validação 1',
            description='A deve estar contido em B',
            message='deve estar contido',
            method='topology.rules.must_be_contained'
        )
        rule.save()
        topology_rule = TopologyRule(
            content_type_a=ContentType.objects.get(app_label='seuapp', model='item'),
            content_type_b=ContentType.objects.get(app_label='seuapp', model='container'),
            geom_field_b='geom',
            rule=rule
        )
        topology_rule.save()

Até aqui, definimos nossa regra topológica, de que A (qualquer instância do modelo Item) deve estar contida em B (Container).

Veja como realizar a validação:

        item_a = Item(geom=unit_polygon(Point(x=0.0, y=0.0, srid=4326)))
        item_a.save()
        container = Container(geom=unit_polygon(Point(x=0.0, y=0.0, srid=4326), size=3))
        container.save()

        errors = topology_rule.validate(item_a)
        len(errors)
>>> 3

No exemplo acima, calculamos os erros diretamente pela regra topológica (TopologyRule) e não foi encontrado nenhum erro.

Os erros gerados pelo TopologyRule não são persistidos no banco de dados, ficando a cargo do desenvolvedor a determinar o melhor momento para tal. As vezes nem será necessário, como por exemplo, em uma validação de formulário Django.

Também desenvolvemos um análogo ao TopologyChecker do QGIS e ele tem o mesmo nome. Sua função é executar todas as validações associadas a um determinado modelo, de uma vez, e persisti-las (ou não).

Veja como usá-lo:

        # definindo as regras
        rule1 = Rule(
            name='A não deve sobrepor A',
            description='A não deve sobrepor A',
            message='sobrepõe',
            method='topology.rules.must_not_overlap'
        )
        rule1.save()
        rule2 = Rule(
            name='A não deve sobrepor B',
            description='A não deve sobrepor B',
            message='sobrepõe',
            method='topology.rules.must_not_overlap_with'
        )
        rule2.save()
        topology_rule1 = TopologyRule(
            content_type_a=ContentType.objects.get(app_label='seuapp', model='item'),
            rule=rule1
        )
        topology_rule1.save()
        topology_rule2 = TopologyRule(
            content_type_a=ContentType.objects.get(app_label='seuapp', model='item'),
            content_type_b=ContentType.objects.get(app_label='seuapp', model='container'),
            geom_field_b='geom',
            rule=rule2
        )
        topology_rule2.save()

        item1 = Item(geom=unit_polygon(Point(x=0.0, y=0.0, srid=4326)))
        item1.save()
        item2 = Item(geom=unit_polygon(Point(x=0.0, y=0.0, srid=4326)))
        item2.save()
        container = Container(geom=unit_polygon(Point(x=0.0, y=0.0, srid=4326), size=2))
        container.save()

        # até aqui, criamos nossas regras e os registros de item e container

        topo_checker = TopologyChecker()

        errors = topo_checker.validate_all(Item)

O TopologyChecker retorna um dicionário, usando como chave o nome da validação executada e uma lista associada, com N erros detectados. No caso acima, o topologychecker encontrou 4 erros:

  • item 1 sobrepõe item 2;
  • item 2 sobrepõe item 1;
  • item 1 sobrepõe Container 1;
  • item 2 sobrepõe COntainer 1;

Estamos usando o Django Topology em produção no Geoadmin um produto que trabalha intensivamente com dados geográficos.

Você pode extender o mesmo com suas próprias validações. Basta criar um método com a seguinte assinatura:

def minha_validacao(rule, feature, **kwargs):  
    # sua validacao aqui
    # deve retornar uma lista de TopologyError (sem salvar no banco)
    if feature.geom.intersects(outra_geometria):
        return [TopologyError()]

    return []

Com este método pronto, você só precisa criar uma Rule, apontando o atributo method para o caminmho qualificado da sua função:

rule = Rule(  
    name='name'
    method='foo.bar.minha_validacao
)

Quer ajudar? Estamos aí, confere o repositório e bora lá.