Skip to content
This repository has been archived by the owner on Apr 8, 2024. It is now read-only.

Commit

Permalink
feat(server): support sample data distribution from datacatalogv3
Browse files Browse the repository at this point in the history
  • Loading branch information
rot1024 committed Mar 15, 2024
1 parent 37763de commit e9a8072
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 96 deletions.
72 changes: 21 additions & 51 deletions server/datacatalog/citygml.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package datacatalog

import (
"context"
"encoding/csv"
"fmt"
"io"
"net/http"
"net/url"
"path"
"slices"
Expand All @@ -16,7 +13,6 @@ import (
"github.com/eukarya-inc/reearth-plateauview/server/plateaucms"
"github.com/reearth/reearthx/util"
"github.com/samber/lo"
"github.com/spkg/bom"
)

type CityGMLFilesResponse struct {
Expand Down Expand Up @@ -73,8 +69,13 @@ func fetchCityGMLFiles(ctx context.Context, r plateauapi.Repo, id string) (*City
return nil, nil
}

maxlodURL := admin["maxlod"].(string)
if maxlodURL == "" {
maxlodURLs, ok := admin["maxlod"].([]string)
if !ok {
return nil, nil
}

citygmlURLs, ok := admin["citygmlUrl"].([]string)
if !ok {
return nil, nil
}

Expand Down Expand Up @@ -106,12 +107,12 @@ func fetchCityGMLFiles(ctx context.Context, r plateauapi.Repo, id string) (*City
gurls = gmlURLs(asset.File.Paths(), assetBase)
}

data, err := fetchCSV(ctx, maxlodURL)
data, err := fetchCSVs(ctx, maxlodURLs, citygmlURLs)
if err != nil {
return nil, err
}

files := csvToCityGMLFilesResponse(data, citygml.URL, gurls)
files := csvToCityGMLFilesResponse(data, gurls)
return &CityGMLFilesResponse{
CityCode: string(citygml.CityCode),
CityName: city.Name,
Expand All @@ -123,59 +124,28 @@ func fetchCityGMLFiles(ctx context.Context, r plateauapi.Repo, id string) (*City
}, nil
}

func fetchCSV(ctx context.Context, url string) (records [][]string, _ error) {
res, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

resp, err := http.DefaultClient.Do(res)
if err != nil {
return nil, fmt.Errorf("failed to request: %w", err)
}

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to request: %w", err)
}

c := csv.NewReader(bom.NewReader(resp.Body))
for {
record, err := c.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("failed to read csv: %w", err)
}
records = append(records, record)
}

return
}

func csvToCityGMLFilesResponse(data [][]string, base string, gmlURLs []*url.URL) CityGMLFiles {
func csvToCityGMLFilesResponse(data [][]string, gmlURLs []*url.URL) CityGMLFiles {
res := make(CityGMLFiles)

for _, record := range data {
if len(record) < 3 || record[0] == "" {
if len(record) < 3 || record[0] == "" || record[1] == "" {
continue
}

if !isNumeric(rune(record[0][0])) {
// it's a header
continue
if !isNumeric(rune(record[1][0])) {
continue // skip header
}

// code,type,maxLod(,path)
meshCode := record[0]
featureType := record[1]
maxlod, _ := strconv.Atoi(record[2])
// base,code,type,maxLod(,path)
base := record[0]
meshCode := record[1]
featureType := record[2]
maxlod, _ := strconv.Atoi(record[3])
citygmlURL := ""
gmlPath := record[4]

if len(record) > 3 && gmlURLs == nil {
citygmlURL = citygmlItemURLFrom(base, record[3], featureType)
if len(record) > 4 && gmlURLs == nil {
citygmlURL = citygmlItemURLFrom(base, gmlPath, featureType)
} else {
// compat for datacatalogv2
prefix := fmt.Sprintf("%s_%s_", meshCode, featureType)
Expand Down
79 changes: 79 additions & 0 deletions server/datacatalog/citygml_csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package datacatalog

import (
"context"
"encoding/csv"
"fmt"
"io"
"net/http"

"github.com/reearth/reearthx/rerror"
"github.com/spkg/bom"
"golang.org/x/sync/errgroup"
)

func fetchCSVs(ctx context.Context, urls, citygmlBaseURLs []string) (records [][]string, _ error) {
if len(urls) != len(citygmlBaseURLs) {
return nil, fmt.Errorf("length of urls and citygmlBaseURLs must be the same")
}

errg := errgroup.Group{}
errg.SetLimit(10)

for i, url := range urls {
url := url
base := citygmlBaseURLs[i]
errg.Go(func() error {
data, err := fetchCSV(ctx, url, base)
if err != nil {
return fmt.Errorf("failed to fetch %s: %w", url, err)
}

records = append(records, data...)
return nil
})
}

if err := errg.Wait(); err != nil {
return nil, err
}

return records, nil
}

func fetchCSV(ctx context.Context, url, prefix string) (records [][]string, _ error) {
res, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

resp, err := http.DefaultClient.Do(res)
if err != nil {
return nil, fmt.Errorf("failed to request: %w", err)
}

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotFound {
return nil, rerror.ErrNotFound
}
return nil, fmt.Errorf("failed to request: %w", err)
}

c := csv.NewReader(bom.NewReader(resp.Body))
for {
record, err := c.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("failed to read csv: %w", err)
}

record = append([]string{prefix}, record...)
records = append(records, record)
}

return
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ func TestNewCache(t *testing.T) {
URL: "https://example.com/13101_tokyo23ku_2022_citygml_op_2.zip",
FeatureTypes: []string{"bldg", "dem"},
Admin: map[string]any{
"maxlod": "maxlod2",
"citygmlUrl": []string{"https://example.com/13101_tokyo23ku_2022_citygml_op_2.zip"},
"maxlod": []string{"maxlod2"},
"citygmlAssetId": "assetid2",
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,8 @@ func citygmlFrom(d datacatalogv2.DataCatalogItem, i *fetcherPlateauItem2) *plate
URL: i.CityGMLURL,
FeatureTypes: i.FeatureTypes,
Admin: map[string]any{
"maxlod": i.MaxLODURL,
"maxlod": []string{i.MaxLODURL},
"citygmlUrl": []string{i.CityGMLURL},
"citygmlAssetId": i.CityGMLAssetID,
},
}
Expand Down
19 changes: 8 additions & 11 deletions server/datacatalog/datacatalogv3/cms_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ func (i *CityItem) SDKStage() stage {
if i.SDKPublic {
return stageGA
}
if i.PlateauStage("") == stageBeta {
return stageBeta
}
return stageAlpha
}

Expand Down Expand Up @@ -151,12 +154,7 @@ type PlateauFeatureItem struct {
Dic string `json:"dic,omitempty" cms:"dic,textarea"`
MaxLOD string `json:"maxlod,omitempty" cms:"maxlod,-"`
// metadata
Status *cms.Tag `json:"status,omitempty" cms:"status,select,metadata"`
Sample bool `json:"sample,omitempty" cms:"sample,bool,metadata"`
}

func (c PlateauFeatureItem) IsPublicForAdmin() bool {
return c.Status != nil && c.Status.Name == string(ManagementStatusReady)
Sample bool `json:"sample,omitempty" cms:"sample,bool,metadata"`
}

func (c PlateauFeatureItem) ReadDic() (d Dic, _ error) {
Expand Down Expand Up @@ -432,11 +430,10 @@ func geospatialjpURL(cityCode string, cityName string, year int) string {
}

type GeospatialjpDataItem struct {
ID string `json:"id,omitempty" cms:"id"`
City string `json:"city,omitempty" cms:"city,reference"`
CityGML string `json:"citygml,omitempty" cms:"citygml,asset"`
MaxLOD string `json:"maxlod,omitempty" cms:"maxlod,asset"`
MaxLODContent [][]string `json:"maxlod_content,omitempty" cms:"-"`
ID string `json:"id,omitempty" cms:"id"`
City string `json:"city,omitempty" cms:"city,reference"`
CityGML string `json:"citygml,omitempty" cms:"citygml,asset"`
MaxLOD string `json:"maxlod,omitempty" cms:"maxlod,asset"`
}

func GeospatialjpDataItemFrom(item *cms.Item) *GeospatialjpDataItem {
Expand Down
85 changes: 58 additions & 27 deletions server/datacatalog/datacatalogv3/conv_citygml.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,76 @@ import (
)

func toCityGMLs(all *AllData, regYear int) (map[plateauapi.ID]*plateauapi.CityGMLDataset, []string) {
cities := all.City
data := all.GeospatialjpDataItems
cmsurl := all.CMSInfo.CMSURL
res := map[plateauapi.ID]*plateauapi.CityGMLDataset{}
resCity := map[string]*plateauapi.CityGMLDataset{}
dataMap := make(map[string]*GeospatialjpDataItem)
cityMap := make(map[string]*CityItem)

dataMap := make(map[string]*plateauapi.CityGMLDataset)

for _, d := range data {
for _, d := range all.GeospatialjpDataItems {
if d.CityGML == "" || d.MaxLOD == "" {
continue
}

dataMap[d.City] = &plateauapi.CityGMLDataset{
URL: d.CityGML,
Admin: map[string]any{
"maxlod": d.MaxLOD,
},
}
dataMap[d.City] = d
}

for _, city := range cities {
if _, ok := dataMap[city.ID]; !ok {
for _, city := range all.City {
data := dataMap[city.ID]
if data == nil {
continue
}

dataMap[city.ID].ID = plateauapi.CityGMLDatasetIDFrom(plateauapi.AreaCode(city.CityCode))
dataMap[city.ID].Year = city.YearInt()
dataMap[city.ID].RegistrationYear = regYear
dataMap[city.ID].PrefectureCode = plateauapi.AreaCode(plateauapi.AreaCode(city.CityCode).PrefectureCode())
dataMap[city.ID].PrefectureID = plateauapi.NewID(dataMap[city.ID].PrefectureCode.String(), plateauapi.TypePrefecture)
dataMap[city.ID].CityID = plateauapi.NewID(city.CityCode, plateauapi.TypeCity)
dataMap[city.ID].CityCode = plateauapi.AreaCode(city.CityCode)
dataMap[city.ID].FeatureTypes = all.FeatureTypesOf(city.ID)
dataMap[city.ID].PlateauSpecMinorID = plateauapi.PlateauSpecIDFrom(city.Spec)
dataMap[city.ID].Admin = newAdmin(city.ID, city.SDKStage(), cmsurl, dataMap[city.ID].Admin)
prefCode := plateauapi.AreaCode(city.CityCode).PrefectureCode()
adminExtra := map[string]any{
"maxlod": []string{data.MaxLOD},
"citygmlUrl": []string{data.CityGML},
}

d := &plateauapi.CityGMLDataset{
ID: plateauapi.CityGMLDatasetIDFrom(plateauapi.AreaCode(city.CityCode)),
URL: data.CityGML,
Year: city.YearInt(),
RegistrationYear: regYear,
PrefectureCode: plateauapi.AreaCode(prefCode),
PrefectureID: plateauapi.NewID(prefCode, plateauapi.TypePrefecture),
CityID: plateauapi.NewID(city.CityCode, plateauapi.TypeCity),
CityCode: plateauapi.AreaCode(city.CityCode),
FeatureTypes: all.FeatureTypesOf(city.ID),
PlateauSpecMinorID: plateauapi.PlateauSpecIDFrom(city.Spec),
Admin: newAdmin(city.ID, city.SDKStage(), cmsurl, adminExtra),
}

cityMap[city.ID] = city
res[d.ID] = d
resCity[data.City] = d
}

res := make(map[plateauapi.ID]*plateauapi.CityGMLDataset)
for _, v := range dataMap {
res[v.ID] = v
// add citygml urls for sample data
for _, data := range all.Plateau {
for _, d := range data {
if !d.Sample || d.MaxLOD == "" || d.CityGML == "" {
continue
}

citygml := resCity[d.City]
if citygml == nil {
continue
}

city := cityMap[d.City]
if city == nil || !city.SDKPublic {
continue
}

maxlod := citygml.Admin.(map[string]any)["maxlod"].([]string)
citygmlURL := citygml.Admin.(map[string]any)["citygmlUrl"].([]string)

maxlod = append(maxlod, d.MaxLOD)
citygmlURL = append(citygmlURL, d.CityGML)

citygml.Admin.(map[string]any)["maxlod"] = maxlod
citygml.Admin.(map[string]any)["citygmlUrl"] = citygmlURL
}
}

return res, nil
Expand Down
Loading

0 comments on commit e9a8072

Please sign in to comment.