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á.