| 573 | |
| 574 | As the Boundary sample service code is available, you can now easily add ConvexHull and Centroid functions as they take exactly the same number of arguments : Only one geometry. The details for implementing and deploying the ConvexHull Service are provided bellow, and we will let you do the same thing for the Centroid one. |
| 575 | |
| 576 | === C Version === |
| 577 | |
| 578 | Please add first the following code to the service.c source code : |
| 579 | |
| 580 | {{{ |
| 581 | #!c |
| 582 | int ConvexHull(maps*& conf,maps*& inputs,maps*& outputs){ |
| 583 | OGRGeometryH geometry,res; |
| 584 | map* tmp=getMapFromMaps(inputs,"InputPolygon","value"); |
| 585 | if(tmp==NULL) |
| 586 | return SERVICE_FAILED; |
| 587 | map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType"); |
| 588 | if(strncmp(tmp1->value,"application/json",16)==0) |
| 589 | geometry=OGR_G_CreateGeometryFromJson(tmp->value); |
| 590 | else |
| 591 | geometry=createGeometryFromWFS(conf,tmp->value); |
| 592 | res=OGR_G_ConvexHull(geometry); |
| 593 | tmp1=getMapFromMaps(outputs,"Result","mimeType"); |
| 594 | if(strncmp(tmp1->value,"application/json",16)==0){ |
| 595 | addToMap(outputs->content,"value",OGR_G_ExportToJson(res)); |
| 596 | addToMap(outputs->content,"mimeType","text/plain"); |
| 597 | } |
| 598 | else{ |
| 599 | addToMap(outputs->content,"value",OGR_G_ExportToGML(res)); |
| 600 | } |
| 601 | outputs->next=NULL; |
| 602 | OGR_G_DestroyGeometry(geometry); |
| 603 | OGR_G_DestroyGeometry(res); |
| 604 | return SERVICE_SUCCEEDED; |
| 605 | } |
| 606 | }}} |
| 607 | |
| 608 | This new code is exactly the same as for the Boundary Service. The only thing we modified is the line where the OGR_G_ConvexHull function is called (rather than the OGR_G_GetBoundary you used before). It is better to not copy and paste the whole function and find a more generic way to define your new Services as the function body will be the same in every case. The following generic function is proposed to make things simpler : |
| 609 | |
| 610 | {{{ |
| 611 | #!c |
| 612 | int applyOne(maps*& conf,maps*& inputs,maps*& outputs,OGRGeometryH (*myFunc) (OGRGeometryH)){ |
| 613 | OGRGeometryH geometry,res; |
| 614 | map* tmp=getMapFromMaps(inputs,"InputPolygon","value"); |
| 615 | if(tmp==NULL) |
| 616 | return SERVICE_FAILED; |
| 617 | map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType"); |
| 618 | if(strncmp(tmp1->value,"application/json",16)==0) |
| 619 | geometry=OGR_G_CreateGeometryFromJson(tmp->value); |
| 620 | else |
| 621 | geometry=createGeometryFromWFS(conf,tmp->value); |
| 622 | res=(*myFunc)(geometry); |
| 623 | tmp1=getMapFromMaps(outputs,"Result","mimeType"); |
| 624 | if(strncmp(tmp1->value,"application/json",16)==0){ |
| 625 | addToMap(outputs->content,"value",OGR_G_ExportToJson(res)); |
| 626 | addToMap(outputs->content,"mimeType","text/plain"); |
| 627 | } |
| 628 | else{ |
| 629 | addToMap(outputs->content,"value",OGR_G_ExportToGML(res)); |
| 630 | } |
| 631 | outputs->next=NULL; |
| 632 | OGR_G_DestroyGeometry(geometry); |
| 633 | OGR_G_DestroyGeometry(res); |
| 634 | return SERVICE_SUCCEEDED; |
| 635 | } |
| 636 | }}} |
| 637 | |
| 638 | Then, a function pointer called myFunc rather than the full function name can be used. This way we can re- implement our Boundary Service this way : |
| 639 | |
| 640 | {{{ |
| 641 | #!c |
| 642 | int Boundary(maps*& conf,maps*& inputs,maps*& outputs){ |
| 643 | return applyOne(conf,inputs,outputs,&OGR_G_GetBoundary); |
| 644 | } |
| 645 | }}} |
| 646 | |
| 647 | Using this applyOne local function defined in the service.c source code, we can define other Services this way : |
| 648 | |
| 649 | {{{ |
| 650 | #!c |
| 651 | int ConvexHull(maps*& conf,maps*& inputs,maps*& outputs){ |
| 652 | return applyOne(conf,inputs,outputs,&OGR_G_ConvexHull); |
| 653 | } |
| 654 | int Centroid(maps*& conf,maps*& inputs,maps*& outputs){ |
| 655 | return applyOne(conf,inputs,outputs,&MY_OGR_G_Centroid); |
| 656 | } |
| 657 | }}} |
| 658 | |
| 659 | The genericity of the applyOne function let you add two new Services in your ZOO Services Provider : ConvexHull and Centroid. |
| 660 | |
| 661 | Note that you should define MY_OGR_Centroid function before the Centroid one as OGR_G_Centroid don't return a geometry object but set the value to an already existing one and support only Polygon as input, so to ensure we use the ConvexHull for MultiPolygon. So please use the code bellow : |
| 662 | |
| 663 | {{{ |
| 664 | #!c |
| 665 | OGRGeometryH MY_OGR_G_Centroid(OGRGeometryH hTarget){ |
| 666 | OGRGeometryH res; |
| 667 | res=OGR_G_CreateGeometryFromJson("{\"type\": \"Point\", \"coordinates\": [0,0] }"); |
| 668 | OGRwkbGeometryType gtype=OGR_G_GetGeometryType(hTarget); |
| 669 | if(gtype!=wkbPolygon){ |
| 670 | hTarget=OGR_G_ConvexHull(hTarget); |
| 671 | } |
| 672 | OGR_G_Centroid(hTarget,res); |
| 673 | return res; |
| 674 | } |
| 675 | }}} |
| 676 | |
| 677 | To deploy your Services, you only have to copy the Boundary.zcfg metadata file from your cgi-env directory as ConvexHull.zcfg and Centroid.zcfg. Then, you must rename the Service name on the first line to be able to run and test the Execute request in the same way you did before. You only have to set the Identifier value to ConvexHull or Centroid in your request depending on the Service you want to run. |
| 678 | |
| 679 | Note here that the GetCapabilities and DescribeProcess requests will return odd results as we didn't modified any metadata informations, you can edit the .zcfg files to set correct values. By the way it can be used for testing purpose, as the input and output get the same name and default/supported formats. |
| 680 | |
| 681 | === Python Version === |
| 682 | |
| 683 | {{{ |
| 684 | #!python |
| 685 | def ConvexHull(conf,inputs,outputs): |
| 686 | if inputs["InputPolygon"]["mimeType"]=="application/json": |
| 687 | geometry=osgeo.ogr.CreateGeometryFromJson(inputs["InputPolygon"]["value"]) |
| 688 | else: |
| 689 | geometry=createGeometryFromWFS(inputs["InputPolygon"]["value"]) |
| 690 | rgeom=geometry.ConvexHull() |
| 691 | if outputs["Result"]["mimeType"]=="application/json": |
| 692 | outputs["Result"]["value"]=rgeom.ExportToJson() |
| 693 | outputs["Result"]["mimeType"]="text/plain" |
| 694 | else: |
| 695 | outputs["Result"]["value"]=rgeom.ExportToGML() |
| 696 | geometry.Destroy() |
| 697 | rgeom.Destroy() |
| 698 | return 3 |
| 699 | }}} |
| 700 | |
| 701 | Once again, you can easily copy and paste the function for Boundary and simply modify the line where the Geometry method was called. Nevertheless, as we did for the C language we will give you a simple way to get things more generic. |
| 702 | |
| 703 | First of all, the first step which consists in extracting the InputPolygon Geometry as it will be used in the same way in each Service functions, so we will first create a function which will do that for us. The same thing can also be done for filling the output value, so we will define another function to do that automaticaly. Here is the code of this two functions (extractInputs and outputResult) : |
| 704 | |
| 705 | {{{ |
| 706 | #!python |
| 707 | def extractInputs(obj): |
| 708 | if obj["mimeType"]=="application/json": |
| 709 | return osgeo.ogr.CreateGeometryFromJson(obj["value"]) |
| 710 | else: |
| 711 | return createGeometryFromWFS(obj["value"]) |
| 712 | return null |
| 713 | |
| 714 | def outputResult(obj,geom): |
| 715 | if obj["mimeType"]=="application/json": |
| 716 | obj["value"]=geom.ExportToJson() |
| 717 | obj["mimeType"]="text/plain" |
| 718 | else: |
| 719 | obj["value"]=geom.ExportToGML() |
| 720 | }}} |
| 721 | |
| 722 | We can so minimize the code of the Boundary function to make it simplier using the following function definition : |
| 723 | |
| 724 | {{{ |
| 725 | #!python |
| 726 | def Boundary(conf,inputs,outputs): |
| 727 | geometry=extractInputs(inputs["InputPolygon"]) |
| 728 | rgeom=geometry.GetBoundary() |
| 729 | outputResult(outputs["Result"],rgeom) |
| 730 | geometry.Destroy() |
| 731 | rgeom.Destroy() |
| 732 | return 3 |
| 733 | }}} |
| 734 | |
| 735 | Then definition of the ConvexHull and Centroid Services can be achieved using the following code : |
| 736 | |
| 737 | {{{ |
| 738 | #!python |
| 739 | def ConvexHull(conf,inputs,outputs): |
| 740 | geometry=extractInputs(inputs["InputPolygon"]) |
| 741 | rgeom=geometry.ConvexHull() |
| 742 | outputResult(outputs["Result"],rgeom) |
| 743 | geometry.Destroy() |
| 744 | rgeom.Destroy() |
| 745 | return 3 |
| 746 | |
| 747 | def Centroid(conf,inputs,outputs): |
| 748 | geometry=extractInputs(inputs["InputPolygon"]) |
| 749 | if geometry.GetGeometryType()!=3: |
| 750 | geometry=geometry.ConvexHull() |
| 751 | rgeom=geometry.Centroid() |
| 752 | outputResult(outputs["Result"],rgeom) |
| 753 | geometry.Destroy() |
| 754 | rgeom.Destroy() |
| 755 | return 3 |
| 756 | }}} |
| 757 | |
| 758 | Note, that in Python you also need to use ConvexHull to deal with MultiPolygons. |
| 759 | |
| 760 | You must now copy the Boundary.zcfg file as we explained for the C version in ConvexHull.zcfg and Centroid.zcfg respectively and then, use make install command to re-deploy and test your Services Provider. |
| 761 | |
| 762 | == Create the Buffer Service == |
| 763 | |
| 764 | We can now work on the Buffer Service, which takes more arguments than the other ones. Indeed, the code is a bit different from the one used to implement the Boundary, ConvexHull and Centroid Services. |
| 765 | |
| 766 | The Buffer service also takes an input geometry, but uses a BufferDistance parameter. It will also allow you to define LitteralData block as the BufferDistance will be simple integer value. The read access to such kind of input value is made using the same function as used before. |
| 767 | |
| 768 | === C Version === |
| 769 | |
| 770 | If you go back to the first Boundary Service source code, you should not find the following very complicated. Indeed, you simply have to add the access of the BufferDistance argument and modify the line whenthe OGR_G_Buffer must be called (instead of OGR_G_GetBoundary). Here is the ful lcode : |
| 771 | |
| 772 | {{{ |
| 773 | int Buffer(maps*& conf,maps*& inputs,maps*& outputs){ |
| 774 | OGRGeometryH geometry,res; |
| 775 | map* tmp1=getMapFromMaps(inputs,"InputPolygon","value"); |
| 776 | if(tmp==NULL) |
| 777 | return SERVICE_FAILED; |
| 778 | map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType"); |
| 779 | if(strncmp(tmp->value,"application/json",16)==0) |
| 780 | geometry=OGR_G_CreateGeometryFromJson(tmp->value); |
| 781 | else |
| 782 | geometry=createGeometryFromWFS(conf,tmp->value); |
| 783 | int bufferDistance=1; |
| 784 | tmp=getMapFromMaps(inputs,"BufferDistance","value"); |
| 785 | if(tmp!=NULL) |
| 786 | bufferDistance=atoi(tmp->value); |
| 787 | res=OGR_G_Buffer(geometry,bufferDistance,30); |
| 788 | tmp1=getMapFromMaps(outputs,"Result","mimeType"); |
| 789 | if(strncmp(tmp1->value,"application/json",16)==0){ |
| 790 | addToMap(outputs->content,"value",OGR_G_ExportToJson(res)); |
| 791 | addToMap(outputs->content,"mimeType","text/plain"); |
| 792 | } |
| 793 | else{ |
| 794 | addToMap(outputs->content,"value",OGR_G_ExportToGML(res)); |
| 795 | } |
| 796 | outputs->next=NULL; |
| 797 | OGR_G_DestroyGeometry(geometry); |
| 798 | OGR_G_DestroyGeometry(res); |
| 799 | return SERVICE_SUCCEEDED; |
| 800 | } |
| 801 | }}} |
| 802 | |
| 803 | The new code must be inserted in your service.c file and need to be recompiled and replace the older version of your ZOO Service Provider in the /usr/lib/cgi-bin/ directory. You must of course place the corresponding ZOO Metadata File in the same directory. |
| 804 | |
| 805 | As we explained before, ZOO Kernel is permissive in the sense that you can pass more arguments than defined in you zcfg file, so let's try using a copy of the Boundary.zcfg file renamed as Buffer.zcfg and containing the Buffer identifier. Then, please test your service using an Execute request as you did before. You will obtain the buffer result in a ResponseDocument. |
| 806 | |
| 807 | You may have noted that the above code check if a BufferDistance input was passed to the service. If not, we will use 1 as the default value, which explains why you do not have to use one more input to your previous queries. |
| 808 | |
| 809 | You can change the BufferDistance value used by your Service to compute Buffer of your geometry by adding it to the DataInputs value in your request. Note that using KVP syntaxe, each DataInputs are separated by a semicolon. |
| 810 | |
| 811 | So, the previous request : |
| 812 | |
| 813 | {{{ |
| 814 | DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%3A8082%2Fgeoserver%2Fows%3FSERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dtopp %3Astates%26SRS%3DEPSG%3A4326%26FeatureID%3Dstates.15 |
| 815 | }}} |
| 816 | |
| 817 | Can now be rewritten this way : |
| 818 | |
| 819 | {{{ |
| 820 | DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%3A8082%2Fgeoserver%2Fows%3FSERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dtopp %3Astates%26SRS%3DEPSG%3A4326%26FeatureID%3Dstates.15;BufferDistance=2 |
| 821 | }}} |
| 822 | |
| 823 | Setting BufferDistance value to 2 would give you a different result, then don't pass any other parameter as we defined 1 as the default value in the source code. |
| 824 | |
| 825 | Here you can find the same query in XML format to use from the http://localhost/test_services.html HTML form : |
| 826 | |
| 827 | {{{ |
| 828 | #!xml |
| 829 | <wps:Execute service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 ../wpsExecute_request.xsda"> |
| 830 | <ows:Identifier>Buffer</ows:Identifier> |
| 831 | <wps:DataInputs> |
| 832 | <wps:Input> |
| 833 | <ows:Identifier>InputPolygon</ows:Identifier> |
| 834 | <ows:Title>Playground area</ows:Title> |
| 835 | <wps:Reference xlink:href="http://localhost:8082/geoserver/ows/?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&typename=topp:states&SRS=EPS |
| 836 | G:4326&FeatureID=states.15"/> |
| 837 | </wps:Input> |
| 838 | <wps:Input> |
| 839 | <ows:Identifier>BufferDistance</ows:Identifier> |
| 840 | <wps:Data> |
| 841 | <wps:LiteralData uom="degree">2</wps:LiteralData> |
| 842 | </wps:Data> |
| 843 | </wps:Input> |
| 844 | </wps:DataInputs> |
| 845 | <wps:ResponseForm> |
| 846 | <wps:ResponseDocument> |
| 847 | <wps:Output> |
| 848 | <ows:Identifier>Buffer</ows:Identifier> |
| 849 | <ows:Title>Area serviced by playground.</ows:Title> |
| 850 | <ows:Abstract>Area within which most users of this playground will live.</ows:Abstract> |
| 851 | </wps:Output> |
| 852 | </wps:ResponseDocument> |
| 853 | </wps:ResponseForm> |
| 854 | </wps:Execute> |
| 855 | }}} |
| 856 | |
| 857 | === Python Version === |
| 858 | |
| 859 | As we already defined the utility functions createGeometryFromWFS and outputResult, the code is as simple as this : |
| 860 | |
| 861 | {{{ |
| 862 | #!python |
| 863 | def Buffer(conf,inputs,outputs): |
| 864 | geometry=extractInputs(inputs["InputPolygon"]) |
| 865 | try: |
| 866 | bdist=int(inputs["BufferDistance"]["value"]) |
| 867 | except: |
| 868 | bdist=10 |
| 869 | rgeom=geometry.Buffer(bdist) |
| 870 | outputResult(outputs["Result"],rgeom) |
| 871 | geometry.Destroy() |
| 872 | rgeom.Destroy() |
| 873 | return 3 |
| 874 | }}} |
| 875 | |
| 876 | We simply added the use of inputs["BufferDistance"]["value"] as arguments of the Geometry instance Buffer method. Once you get this code added to your ogr_ws_service_provider.py file, simply copy it in the ZOO Kernel directory (or type make install from your ZOO Service Provider root directory). Note that you also need the Buffer.zcfg file detailled in the next section. |
| 877 | |
| 878 | === The Buffer MetadataFile file === |
| 879 | |
| 880 | You must add BufferDistance to the Service Metadata File to let clients know that this Service supports this parameter. To do this, please copy your orginal Boundary.zcfg file as Buffer.zcfg and add the following lines to the DataInputs block : |
| 881 | |
| 882 | {{{ |
| 883 | [BufferDistance] |
| 884 | Title = Buffer Distance |
| 885 | Abstract = Distance to be used to calculate buffer. |
| 886 | minOccurs = 0 |
| 887 | maxOccurs = 1 |
| 888 | <LiteralData> |
| 889 | DataType = float |
| 890 | <Default> |
| 891 | uom = degree |
| 892 | value = 10 |
| 893 | </Default> |
| 894 | <Supported> |
| 895 | uom = meter |
| 896 | </Supported> |
| 897 | </LiteralData> |
| 898 | }}} |
| 899 | |
| 900 | Note that as minOccurs is set to 0 which means that the input parameter is optional and don't have to be passed. You must know that ZOO Kernel will pass the default value to the Service function for an optional parameter with a default value set. |
| 901 | |
| 902 | You can get a full copy of the Buffer.zcfg file here : |
| 903 | |
| 904 | http://zoo-project.org/trac/browser/trunk/zoo-services/ogr/base-vect-ops/cgi-env/Buffer.zcfg |
| 905 | |
| 906 | You can now ask ZOO Kernel for GetCapabilities, DescribeProcess and Execute for the Buffer Service. |
| 907 | |